1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.nfc.utils; 18 19 import static android.Manifest.permission.MANAGE_DEFAULT_APPLICATIONS; 20 import static android.Manifest.permission.WRITE_SECURE_SETTINGS; 21 22 import android.app.role.RoleManager; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.pm.PackageManager; 26 import android.nfc.NfcAdapter; 27 28 import com.android.nfc.service.AccessService; 29 import com.android.nfc.service.LargeNumAidsService; 30 import com.android.nfc.service.OffHostService; 31 import com.android.nfc.service.PaymentService1; 32 import com.android.nfc.service.PaymentService2; 33 import com.android.nfc.service.PaymentServiceDynamicAids; 34 import com.android.nfc.service.PrefixAccessService; 35 import com.android.nfc.service.PrefixPaymentService1; 36 import com.android.nfc.service.PrefixPaymentService2; 37 import com.android.nfc.service.PrefixTransportService1; 38 import com.android.nfc.service.PrefixTransportService2; 39 import com.android.nfc.service.ScreenOffPaymentService; 40 import com.android.nfc.service.ScreenOnOnlyOffHostService; 41 import com.android.nfc.service.ThroughputService; 42 import com.android.nfc.service.PollingLoopService; 43 import com.android.nfc.service.TransportService1; 44 import com.android.nfc.service.TransportService2; 45 46 import com.google.common.util.concurrent.MoreExecutors; 47 48 import java.util.HashMap; 49 import java.util.concurrent.CountDownLatch; 50 import java.util.concurrent.TimeUnit; 51 import java.util.concurrent.atomic.AtomicReference; 52 53 /** Utilites for multi-device HCE tests. */ 54 public final class HceUtils { 55 HceUtils()56 private HceUtils() {} 57 58 public static final String MC_AID = "A0000000041010"; 59 public static final String PPSE_AID = "325041592E5359532E4444463031"; 60 public static final String VISA_AID = "A0000000030000"; 61 62 public static final String TRANSPORT_AID = "F001020304"; 63 public static final String SE_AID_1 = "A000000151000000"; 64 public static final String SE_AID_2 = "A000000003000000"; 65 public static final String ACCESS_AID = "F005060708"; 66 67 public static final String TRANSPORT_PREFIX_AID = "F001020304"; 68 public static final String ACCESS_PREFIX_AID = "F005060708"; 69 70 public static final String LARGE_NUM_AIDS_PREFIX = "F00102030414"; 71 public static final String LARGE_NUM_AIDS_POSTFIX = "81"; 72 73 /** Service-specific APDU Command/Response sequences */ 74 public static final HashMap<String, CommandApdu[]> COMMAND_APDUS_BY_SERVICE = new HashMap<>(); 75 76 public static final HashMap<String, String[]> RESPONSE_APDUS_BY_SERVICE = new HashMap<>(); 77 78 static { 79 COMMAND_APDUS_BY_SERVICE.put( TransportService1.class.getName()80 TransportService1.class.getName(), 81 new CommandApdu[] { 82 buildSelectApdu(TRANSPORT_AID, true), buildCommandApdu("80CA01E000", true) 83 }); 84 85 RESPONSE_APDUS_BY_SERVICE.put( TransportService1.class.getName()86 TransportService1.class.getName(), new String[] {"80CA9000", "83947102829000"}); 87 88 // Payment Service #1 89 COMMAND_APDUS_BY_SERVICE.put( PaymentService1.class.getName()90 PaymentService1.class.getName(), 91 new CommandApdu[] { 92 buildSelectApdu(PPSE_AID, true), 93 buildSelectApdu(MC_AID, true), 94 buildCommandApdu("80CA01F000", true) 95 }); 96 RESPONSE_APDUS_BY_SERVICE.put( PaymentService1.class.getName()97 PaymentService1.class.getName(), 98 new String[] {"FFFF9000", "FFEF9000", "FFDFFFAABB9000"}); 99 100 COMMAND_APDUS_BY_SERVICE.put( PaymentService2.class.getName()101 PaymentService2.class.getName(), 102 new CommandApdu[] {buildSelectApdu(PPSE_AID, true), buildSelectApdu(MC_AID, true)}); 103 RESPONSE_APDUS_BY_SERVICE.put( PaymentService2.class.getName()104 PaymentService2.class.getName(), new String[] {"12349000", "56789000"}); 105 106 COMMAND_APDUS_BY_SERVICE.put( PaymentServiceDynamicAids.class.getName()107 PaymentServiceDynamicAids.class.getName(), 108 new CommandApdu[] { 109 buildSelectApdu(PPSE_AID, true), 110 buildSelectApdu(VISA_AID, true), 111 buildCommandApdu("80CA01F000", true) 112 }); 113 RESPONSE_APDUS_BY_SERVICE.put( PaymentServiceDynamicAids.class.getName()114 PaymentServiceDynamicAids.class.getName(), 115 new String[] {"FFFF9000", "FF0F9000", "FFDFFFAACB9000"}); 116 117 COMMAND_APDUS_BY_SERVICE.put( PrefixPaymentService1.class.getName()118 PrefixPaymentService1.class.getName(), 119 new CommandApdu[] { 120 buildSelectApdu(PPSE_AID, true), 121 buildSelectApdu(MC_AID, true), 122 buildCommandApdu("80CA01F000", true) 123 }); 124 125 RESPONSE_APDUS_BY_SERVICE.put( PrefixPaymentService1.class.getName()126 PrefixPaymentService1.class.getName(), 127 new String[] {"F1239000", "F4569000", "F789FFAABB9000"}); 128 129 COMMAND_APDUS_BY_SERVICE.put( PrefixPaymentService2.class.getName()130 PrefixPaymentService2.class.getName(), 131 new CommandApdu[] { 132 buildSelectApdu(PPSE_AID, true), 133 buildSelectApdu(MC_AID, true), 134 buildCommandApdu("80CA02F000", true), 135 buildSelectApdu("F0000000FFFFFFFFFFFFFFFFFFFFFFFF", true), 136 buildSelectApdu("F000000000", true) 137 }); 138 139 RESPONSE_APDUS_BY_SERVICE.put( PrefixPaymentService2.class.getName()140 PrefixPaymentService2.class.getName(), 141 new String[] { 142 "FAAA9000", "FBBB9000", "F789FFCCDD9000", "FFBAFEBECA", "F0BABEFECA" 143 }); 144 145 COMMAND_APDUS_BY_SERVICE.put( OffHostService.class.getName()146 OffHostService.class.getName(), 147 new CommandApdu[]{ 148 buildSelectApdu(SE_AID_1, true), 149 buildCommandApdu("80CA9F7F00", true), 150 buildSelectApdu(SE_AID_2, true), 151 buildCommandApdu("80CA9F7F00", true) 152 }); 153 RESPONSE_APDUS_BY_SERVICE.put( OffHostService.class.getName()154 OffHostService.class.getName(), 155 new String[] {"*", "*", "*", "*"} 156 ); 157 COMMAND_APDUS_BY_SERVICE.put( TransportService2.class.getName()158 TransportService2.class.getName(), 159 new CommandApdu[] { 160 buildSelectApdu(TRANSPORT_AID, true), buildCommandApdu("80CA01E100", true) 161 }); 162 RESPONSE_APDUS_BY_SERVICE.put( TransportService2.class.getName()163 TransportService2.class.getName(), new String[] {"81CA9000", "7483624748FEFE9000"}); 164 165 COMMAND_APDUS_BY_SERVICE.put( AccessService.class.getName()166 AccessService.class.getName(), 167 new CommandApdu[] { 168 buildSelectApdu(ACCESS_AID, true), buildCommandApdu("80CA01F000", true) 169 }); 170 RESPONSE_APDUS_BY_SERVICE.put( AccessService.class.getName()171 AccessService.class.getName(), new String[] {"123456789000", "1481148114819000"}); 172 173 COMMAND_APDUS_BY_SERVICE.put( PrefixTransportService1.class.getName()174 PrefixTransportService1.class.getName(), 175 new CommandApdu[] { 176 buildSelectApdu(TRANSPORT_PREFIX_AID + "FFFF", true), 177 buildSelectApdu(TRANSPORT_PREFIX_AID + "FFAA", true), 178 buildSelectApdu(TRANSPORT_PREFIX_AID + "FFAABBCCDDEEFF", true), 179 buildCommandApdu("80CA01FFAA", true) 180 }); 181 RESPONSE_APDUS_BY_SERVICE.put( PrefixTransportService1.class.getName()182 PrefixTransportService1.class.getName(), 183 new String[] { 184 "25929000", "FFEF25929000", "FFDFFFAABB25929000", "FFDFFFAACC25929000" 185 }); 186 187 COMMAND_APDUS_BY_SERVICE.put( PrefixTransportService2.class.getName()188 PrefixTransportService2.class.getName(), 189 new CommandApdu[] { 190 buildSelectApdu(TRANSPORT_PREFIX_AID + "FFFF", true), 191 buildSelectApdu(TRANSPORT_PREFIX_AID + "FFAA", true), 192 buildSelectApdu(TRANSPORT_PREFIX_AID + "FFAABBCCDDEEFF", true), 193 buildCommandApdu("80CA01FFBB", true) 194 }); 195 RESPONSE_APDUS_BY_SERVICE.put( PrefixTransportService2.class.getName()196 PrefixTransportService2.class.getName(), 197 new String[] { 198 "36039000", "FFBB25929000", "FFDFFFBBBB25929000", "FFDFFFBBCC25929000" 199 }); 200 201 COMMAND_APDUS_BY_SERVICE.put( PrefixAccessService.class.getName()202 PrefixAccessService.class.getName(), 203 new CommandApdu[] { 204 buildSelectApdu(ACCESS_PREFIX_AID + "FFFF", true), 205 buildSelectApdu(ACCESS_PREFIX_AID + "FFAA", true), 206 buildSelectApdu(ACCESS_PREFIX_AID + "FFAABBCCDDEEFF", true), 207 buildCommandApdu("80CA010000010203", true) 208 }); 209 RESPONSE_APDUS_BY_SERVICE.put( PrefixAccessService.class.getName()210 PrefixAccessService.class.getName(), 211 new String[] { 212 "FAFE9000", "FAFE25929000", "FAFEAABB25929000", "FAFEFFAACC25929000" 213 }); 214 215 COMMAND_APDUS_BY_SERVICE.put( ThroughputService.class.getName()216 ThroughputService.class.getName(), 217 new CommandApdu[] { 218 buildSelectApdu("F0010203040607FF", true), 219 buildCommandApdu("80CA010100", true), 220 buildCommandApdu("80CA010200", true), 221 buildCommandApdu("80CA010300", true), 222 buildCommandApdu("80CA010400", true), 223 buildCommandApdu("80CA010500", true), 224 buildCommandApdu("80CA010600", true), 225 buildCommandApdu("80CA010700", true), 226 buildCommandApdu("80CA010800", true), 227 buildCommandApdu("80CA010900", true), 228 buildCommandApdu("80CA010A00", true), 229 buildCommandApdu("80CA010B00", true), 230 buildCommandApdu("80CA010C00", true), 231 buildCommandApdu("80CA010D00", true), 232 buildCommandApdu("80CA010E00", true), 233 buildCommandApdu("80CA010F00", true), 234 }); 235 236 RESPONSE_APDUS_BY_SERVICE.put( ThroughputService.class.getName()237 ThroughputService.class.getName(), 238 new String[] { 239 "9000", 240 "0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 241 "0001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 242 "0002FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 243 "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 244 "0004FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 245 "0005FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 246 "0006FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 247 "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 248 "0008FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 249 "0009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 250 "000AFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 251 "000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 252 "000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 253 "000DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 254 "000EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 255 }); 256 257 CommandApdu[] largeCommandSequence = new CommandApdu[256]; 258 String[] largeResponseSequence = new String[256]; 259 for (int i = 0; i < 256; ++i) { 260 largeCommandSequence[i] = 261 buildSelectApdu( 262 LARGE_NUM_AIDS_PREFIX 263 + String.format("%02X", i) 264 + LARGE_NUM_AIDS_POSTFIX, 265 true); 266 largeResponseSequence[i] = "9000" + String.format("%02X", i); 267 } 268 LargeNumAidsService.class.getName()269 COMMAND_APDUS_BY_SERVICE.put(LargeNumAidsService.class.getName(), largeCommandSequence); LargeNumAidsService.class.getName()270 RESPONSE_APDUS_BY_SERVICE.put(LargeNumAidsService.class.getName(), largeResponseSequence); 271 272 COMMAND_APDUS_BY_SERVICE.put( ScreenOffPaymentService.class.getName()273 ScreenOffPaymentService.class.getName(), 274 new CommandApdu[] { 275 buildSelectApdu(HceUtils.PPSE_AID, true), 276 buildSelectApdu(HceUtils.MC_AID, true), 277 buildCommandApdu("80CA01F000", true) 278 }); 279 RESPONSE_APDUS_BY_SERVICE.put( ScreenOffPaymentService.class.getName()280 ScreenOffPaymentService.class.getName(), 281 new String[] {"FFFF9000", "FFEF9000", "FFDFFFAABB9000"}); 282 283 COMMAND_APDUS_BY_SERVICE.put( ScreenOnOnlyOffHostService.class.getName()284 ScreenOnOnlyOffHostService.class.getName(), 285 new CommandApdu[] { 286 buildSelectApdu("A000000476416E64726F696443545340", true), 287 }); 288 RESPONSE_APDUS_BY_SERVICE.put( ScreenOnOnlyOffHostService.class.getName()289 ScreenOnOnlyOffHostService.class.getName(), new String[] {"*"}); 290 291 COMMAND_APDUS_BY_SERVICE.put( PollingLoopService.class.getName()292 PollingLoopService.class.getName(), 293 new CommandApdu[] {buildSelectApdu(HceUtils.ACCESS_AID, true), 294 buildCommandApdu("80CA01F000", true) 295 }); 296 RESPONSE_APDUS_BY_SERVICE.put( PollingLoopService.class.getName()297 PollingLoopService.class.getName(), 298 new String[] {"123456789000", "1481148114819000"} 299 ); 300 } 301 302 /** Enables specified component */ enableComponent(PackageManager pm, ComponentName component)303 public static void enableComponent(PackageManager pm, ComponentName component) { 304 pm.setComponentEnabledSetting( 305 component, 306 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 307 PackageManager.DONT_KILL_APP); 308 } 309 310 /** Disables specified component */ disableComponent(PackageManager pm, ComponentName component)311 public static void disableComponent(PackageManager pm, ComponentName component) { 312 pm.setComponentEnabledSetting( 313 component, 314 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 315 PackageManager.DONT_KILL_APP); 316 } 317 318 /** Converts a byte array to hex string */ getHexBytes(String header, byte[] bytes)319 public static String getHexBytes(String header, byte[] bytes) { 320 StringBuilder sb = new StringBuilder(); 321 if (header != null) { 322 sb.append(header + ": "); 323 } 324 for (byte b : bytes) { 325 sb.append(String.format("%02X ", b)); 326 } 327 return sb.toString(); 328 } 329 330 /** Converts a hex string to byte array */ hexStringToBytes(String s)331 public static byte[] hexStringToBytes(String s) { 332 if (s == null || s.length() == 0) return null; 333 int len = s.length(); 334 if (len % 2 != 0) { 335 s = '0' + s; 336 len++; 337 } 338 byte[] data = new byte[len / 2]; 339 for (int i = 0; i < len; i += 2) { 340 data[i / 2] = 341 (byte) 342 ((Character.digit(s.charAt(i), 16) << 4) 343 + Character.digit(s.charAt(i + 1), 16)); 344 } 345 return data; 346 } 347 348 /** Builds a command APDU from given string */ buildCommandApdu(String apdu, boolean reachable)349 public static CommandApdu buildCommandApdu(String apdu, boolean reachable) { 350 return new CommandApdu(apdu, reachable); 351 } 352 353 /** Builds a select AID command APDU */ buildSelectApdu(String aid, boolean reachable)354 public static CommandApdu buildSelectApdu(String aid, boolean reachable) { 355 String apdu = String.format("00A40400%02X%s", aid.length() / 2, aid); 356 return new CommandApdu(apdu, reachable); 357 } 358 359 /** Sets default wallet role holder to given package name */ setDefaultWalletRoleHolder(Context context, String packageName)360 public static boolean setDefaultWalletRoleHolder(Context context, String packageName) { 361 RoleManager roleManager = context.getSystemService(RoleManager.class); 362 CountDownLatch countDownLatch = new CountDownLatch(1); 363 AtomicReference<Boolean> result = new AtomicReference<>(false); 364 try { 365 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation() 366 .getUiAutomation() 367 .adoptShellPermissionIdentity(MANAGE_DEFAULT_APPLICATIONS); 368 assert roleManager != null; 369 roleManager.setDefaultApplication( 370 RoleManager.ROLE_WALLET, 371 packageName, 372 0, 373 MoreExecutors.directExecutor(), 374 aBoolean -> { 375 result.set(aBoolean); 376 countDownLatch.countDown(); 377 }); 378 countDownLatch.await(3000, TimeUnit.MILLISECONDS); 379 } catch (InterruptedException e) { 380 throw new RuntimeException(e); 381 } finally { 382 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation() 383 .getUiAutomation() 384 .dropShellPermissionIdentity(); 385 } 386 return result.get(); 387 } 388 389 /** Disables secure NFC so that NFC works with screen off */ disableSecureNfc(NfcAdapter adapter)390 public static boolean disableSecureNfc(NfcAdapter adapter) { 391 boolean res = false; 392 try { 393 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation() 394 .getUiAutomation() 395 .adoptShellPermissionIdentity(WRITE_SECURE_SETTINGS); 396 res = adapter.enableSecureNfc(false); 397 } finally { 398 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation() 399 .getUiAutomation() 400 .dropShellPermissionIdentity(); 401 } 402 return res; 403 } 404 } 405