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.PollingLoopService; 35 import com.android.nfc.service.PrefixAccessService; 36 import com.android.nfc.service.PrefixPaymentService1; 37 import com.android.nfc.service.PrefixPaymentService2; 38 import com.android.nfc.service.PrefixTransportService1; 39 import com.android.nfc.service.PrefixTransportService2; 40 import com.android.nfc.service.ScreenOffPaymentService; 41 import com.android.nfc.service.ScreenOnOnlyOffHostService; 42 import com.android.nfc.service.ThroughputService; 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 public static final String EMULATOR_PACKAGE_NAME = "com.android.nfc.emulator"; 74 75 /** Service-specific APDU Command/Response sequences */ 76 public static final HashMap<String, CommandApdu[]> COMMAND_APDUS_BY_SERVICE = new HashMap<>(); 77 78 public static final HashMap<String, String[]> RESPONSE_APDUS_BY_SERVICE = new HashMap<>(); 79 80 static { 81 COMMAND_APDUS_BY_SERVICE.put( TransportService1.class.getName()82 TransportService1.class.getName(), 83 new CommandApdu[] { 84 buildSelectApdu(TRANSPORT_AID, true), buildCommandApdu("80CA01E000", true) 85 }); 86 87 RESPONSE_APDUS_BY_SERVICE.put( TransportService1.class.getName()88 TransportService1.class.getName(), new String[] {"80CA9000", "83947102829000"}); 89 90 // Payment Service #1 91 COMMAND_APDUS_BY_SERVICE.put( PaymentService1.class.getName()92 PaymentService1.class.getName(), 93 new CommandApdu[] { 94 buildSelectApdu(PPSE_AID, true), 95 buildSelectApdu(MC_AID, true), 96 buildCommandApdu("80CA01F000", true) 97 }); 98 RESPONSE_APDUS_BY_SERVICE.put( PaymentService1.class.getName()99 PaymentService1.class.getName(), 100 new String[] {"FFFF9000", "FFEF9000", "FFDFFFAABB9000"}); 101 102 COMMAND_APDUS_BY_SERVICE.put( PaymentService2.class.getName()103 PaymentService2.class.getName(), 104 new CommandApdu[] {buildSelectApdu(PPSE_AID, true), buildSelectApdu(MC_AID, true)}); 105 RESPONSE_APDUS_BY_SERVICE.put( PaymentService2.class.getName()106 PaymentService2.class.getName(), new String[] {"12349000", "56789000"}); 107 108 COMMAND_APDUS_BY_SERVICE.put( PaymentServiceDynamicAids.class.getName()109 PaymentServiceDynamicAids.class.getName(), 110 new CommandApdu[] { 111 buildSelectApdu(PPSE_AID, true), 112 buildSelectApdu(VISA_AID, true), 113 buildCommandApdu("80CA01F000", true) 114 }); 115 RESPONSE_APDUS_BY_SERVICE.put( PaymentServiceDynamicAids.class.getName()116 PaymentServiceDynamicAids.class.getName(), 117 new String[] {"FFFF9000", "FF0F9000", "FFDFFFAACB9000"}); 118 119 COMMAND_APDUS_BY_SERVICE.put( PrefixPaymentService1.class.getName()120 PrefixPaymentService1.class.getName(), 121 new CommandApdu[] { 122 buildSelectApdu(PPSE_AID, true), 123 buildSelectApdu(MC_AID, true), 124 buildCommandApdu("80CA01F000", true) 125 }); 126 127 RESPONSE_APDUS_BY_SERVICE.put( PrefixPaymentService1.class.getName()128 PrefixPaymentService1.class.getName(), 129 new String[] {"F1239000", "F4569000", "F789FFAABB9000"}); 130 131 COMMAND_APDUS_BY_SERVICE.put( PrefixPaymentService2.class.getName()132 PrefixPaymentService2.class.getName(), 133 new CommandApdu[] { 134 buildSelectApdu(PPSE_AID, true), 135 buildSelectApdu(MC_AID, true), 136 buildCommandApdu("80CA02F000", true), 137 buildSelectApdu("F0000000FFFFFFFFFFFFFFFFFFFFFFFF", true), 138 buildSelectApdu("F000000000", true) 139 }); 140 141 RESPONSE_APDUS_BY_SERVICE.put( PrefixPaymentService2.class.getName()142 PrefixPaymentService2.class.getName(), 143 new String[] { 144 "FAAA9000", "FBBB9000", "F789FFCCDD9000", "FFBAFEBECA", "F0BABEFECA" 145 }); 146 147 COMMAND_APDUS_BY_SERVICE.put( OffHostService.class.getName()148 OffHostService.class.getName(), 149 new CommandApdu[]{ 150 buildSelectApdu(SE_AID_1, true), 151 buildCommandApdu("80CA9F7F00", true), 152 buildSelectApdu(SE_AID_2, true), 153 buildCommandApdu("80CA9F7F00", true) 154 }); 155 RESPONSE_APDUS_BY_SERVICE.put( OffHostService.class.getName()156 OffHostService.class.getName(), 157 new String[] {"*", "*", "*", "*"} 158 ); 159 COMMAND_APDUS_BY_SERVICE.put( TransportService2.class.getName()160 TransportService2.class.getName(), 161 new CommandApdu[] { 162 buildSelectApdu(TRANSPORT_AID, true), buildCommandApdu("80CA01E100", true) 163 }); 164 RESPONSE_APDUS_BY_SERVICE.put( TransportService2.class.getName()165 TransportService2.class.getName(), new String[] {"81CA9000", "7483624748FEFE9000"}); 166 167 COMMAND_APDUS_BY_SERVICE.put( AccessService.class.getName()168 AccessService.class.getName(), 169 new CommandApdu[] { 170 buildSelectApdu(ACCESS_AID, true), buildCommandApdu("80CA01F000", true) 171 }); 172 RESPONSE_APDUS_BY_SERVICE.put( AccessService.class.getName()173 AccessService.class.getName(), new String[] {"123456789000", "1481148114819000"}); 174 175 COMMAND_APDUS_BY_SERVICE.put( PrefixTransportService1.class.getName()176 PrefixTransportService1.class.getName(), 177 new CommandApdu[] { 178 buildSelectApdu(TRANSPORT_PREFIX_AID + "FFFF", true), 179 buildSelectApdu(TRANSPORT_PREFIX_AID + "FFAA", true), 180 buildSelectApdu(TRANSPORT_PREFIX_AID + "FFAABBCCDDEEFF", true), 181 buildCommandApdu("80CA01FFAA", true) 182 }); 183 RESPONSE_APDUS_BY_SERVICE.put( PrefixTransportService1.class.getName()184 PrefixTransportService1.class.getName(), 185 new String[] { 186 "25929000", "FFEF25929000", "FFDFFFAABB25929000", "FFDFFFAACC25929000" 187 }); 188 189 COMMAND_APDUS_BY_SERVICE.put( PrefixTransportService2.class.getName()190 PrefixTransportService2.class.getName(), 191 new CommandApdu[] { 192 buildSelectApdu(TRANSPORT_PREFIX_AID + "FFFF", true), 193 buildSelectApdu(TRANSPORT_PREFIX_AID + "FFAA", true), 194 buildSelectApdu(TRANSPORT_PREFIX_AID + "FFAABBCCDDEEFF", true), 195 buildCommandApdu("80CA01FFBB", true) 196 }); 197 RESPONSE_APDUS_BY_SERVICE.put( PrefixTransportService2.class.getName()198 PrefixTransportService2.class.getName(), 199 new String[] { 200 "36039000", "FFBB25929000", "FFDFFFBBBB25929000", "FFDFFFBBCC25929000" 201 }); 202 203 COMMAND_APDUS_BY_SERVICE.put( PrefixAccessService.class.getName()204 PrefixAccessService.class.getName(), 205 new CommandApdu[] { 206 buildSelectApdu(ACCESS_PREFIX_AID + "FFFF", true), 207 buildSelectApdu(ACCESS_PREFIX_AID + "FFAA", true), 208 buildSelectApdu(ACCESS_PREFIX_AID + "FFAABBCCDDEEFF", true), 209 buildCommandApdu("80CA010000010203", true) 210 }); 211 RESPONSE_APDUS_BY_SERVICE.put( PrefixAccessService.class.getName()212 PrefixAccessService.class.getName(), 213 new String[] { 214 "FAFE9000", "FAFE25929000", "FAFEAABB25929000", "FAFEFFAACC25929000" 215 }); 216 217 COMMAND_APDUS_BY_SERVICE.put( ThroughputService.class.getName()218 ThroughputService.class.getName(), 219 new CommandApdu[] { 220 buildSelectApdu("F0010203040607FF", true), 221 buildCommandApdu("80CA010100", true), 222 buildCommandApdu("80CA010200", true), 223 buildCommandApdu("80CA010300", true), 224 buildCommandApdu("80CA010400", true), 225 buildCommandApdu("80CA010500", true), 226 buildCommandApdu("80CA010600", true), 227 buildCommandApdu("80CA010700", true), 228 buildCommandApdu("80CA010800", true), 229 buildCommandApdu("80CA010900", true), 230 buildCommandApdu("80CA010A00", true), 231 buildCommandApdu("80CA010B00", true), 232 buildCommandApdu("80CA010C00", true), 233 buildCommandApdu("80CA010D00", true), 234 buildCommandApdu("80CA010E00", true), 235 buildCommandApdu("80CA010F00", true), 236 }); 237 238 RESPONSE_APDUS_BY_SERVICE.put( ThroughputService.class.getName()239 ThroughputService.class.getName(), 240 new String[] { 241 "9000", 242 "0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 243 "0001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 244 "0002FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 245 "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 246 "0004FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 247 "0005FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 248 "0006FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 249 "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 250 "0008FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 251 "0009FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 252 "000AFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 253 "000BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 254 "000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 255 "000DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 256 "000EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9000", 257 }); 258 259 CommandApdu[] largeCommandSequence = new CommandApdu[256]; 260 String[] largeResponseSequence = new String[256]; 261 for (int i = 0; i < 256; ++i) { 262 largeCommandSequence[i] = 263 buildSelectApdu( 264 LARGE_NUM_AIDS_PREFIX 265 + String.format("%02X", i) 266 + LARGE_NUM_AIDS_POSTFIX, 267 true); 268 largeResponseSequence[i] = "9000" + String.format("%02X", i); 269 } 270 LargeNumAidsService.class.getName()271 COMMAND_APDUS_BY_SERVICE.put(LargeNumAidsService.class.getName(), largeCommandSequence); LargeNumAidsService.class.getName()272 RESPONSE_APDUS_BY_SERVICE.put(LargeNumAidsService.class.getName(), largeResponseSequence); 273 274 COMMAND_APDUS_BY_SERVICE.put( ScreenOffPaymentService.class.getName()275 ScreenOffPaymentService.class.getName(), 276 new CommandApdu[] { 277 buildSelectApdu(HceUtils.PPSE_AID, true), 278 buildSelectApdu(HceUtils.MC_AID, true), 279 buildCommandApdu("80CA01F000", true) 280 }); 281 RESPONSE_APDUS_BY_SERVICE.put( ScreenOffPaymentService.class.getName()282 ScreenOffPaymentService.class.getName(), 283 new String[] {"FFFF9000", "FFEF9000", "FFDFFFAABB9000"}); 284 285 COMMAND_APDUS_BY_SERVICE.put( ScreenOnOnlyOffHostService.class.getName()286 ScreenOnOnlyOffHostService.class.getName(), 287 new CommandApdu[] { 288 buildSelectApdu("A000000476416E64726F696443545340", true), 289 }); 290 RESPONSE_APDUS_BY_SERVICE.put( ScreenOnOnlyOffHostService.class.getName()291 ScreenOnOnlyOffHostService.class.getName(), new String[] {"*"}); 292 293 COMMAND_APDUS_BY_SERVICE.put( PollingLoopService.class.getName()294 PollingLoopService.class.getName(), 295 new CommandApdu[] {buildSelectApdu(HceUtils.ACCESS_AID, true), 296 buildCommandApdu("80CA01F000", true) 297 }); 298 RESPONSE_APDUS_BY_SERVICE.put( PollingLoopService.class.getName()299 PollingLoopService.class.getName(), 300 new String[] {"123456789000", "1481148114819000"} 301 ); 302 } 303 304 /** Enables specified component */ enableComponent(PackageManager pm, ComponentName component)305 public static void enableComponent(PackageManager pm, ComponentName component) { 306 pm.setComponentEnabledSetting( 307 component, 308 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 309 PackageManager.DONT_KILL_APP); 310 } 311 312 /** Disables specified component */ disableComponent(PackageManager pm, ComponentName component)313 public static void disableComponent(PackageManager pm, ComponentName component) { 314 pm.setComponentEnabledSetting( 315 component, 316 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 317 PackageManager.DONT_KILL_APP); 318 } 319 320 /** Converts a byte array to hex string */ getHexBytes(String header, byte[] bytes)321 public static String getHexBytes(String header, byte[] bytes) { 322 StringBuilder sb = new StringBuilder(); 323 if (header != null) { 324 sb.append(header + ": "); 325 } 326 for (byte b : bytes) { 327 sb.append(String.format("%02X ", b)); 328 } 329 return sb.toString(); 330 } 331 332 /** Converts a hex string to byte array */ hexStringToBytes(String s)333 public static byte[] hexStringToBytes(String s) { 334 if (s == null || s.length() == 0) return null; 335 int len = s.length(); 336 if (len % 2 != 0) { 337 s = '0' + s; 338 len++; 339 } 340 byte[] data = new byte[len / 2]; 341 for (int i = 0; i < len; i += 2) { 342 data[i / 2] = 343 (byte) 344 ((Character.digit(s.charAt(i), 16) << 4) 345 + Character.digit(s.charAt(i + 1), 16)); 346 } 347 return data; 348 } 349 350 /** Builds a command APDU from given string */ buildCommandApdu(String apdu, boolean reachable)351 public static CommandApdu buildCommandApdu(String apdu, boolean reachable) { 352 return new CommandApdu(apdu, reachable); 353 } 354 355 /** Builds a select AID command APDU */ buildSelectApdu(String aid, boolean reachable)356 public static CommandApdu buildSelectApdu(String aid, boolean reachable) { 357 String apdu = String.format("00A40400%02X%s", aid.length() / 2, aid); 358 return new CommandApdu(apdu, reachable); 359 } 360 361 /** Sets default wallet role holder to given package name */ setDefaultWalletRoleHolder(Context context, String packageName)362 public static boolean setDefaultWalletRoleHolder(Context context, String packageName) { 363 RoleManager roleManager = context.getSystemService(RoleManager.class); 364 CountDownLatch countDownLatch = new CountDownLatch(1); 365 AtomicReference<Boolean> result = new AtomicReference<>(false); 366 try { 367 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation() 368 .getUiAutomation() 369 .adoptShellPermissionIdentity(MANAGE_DEFAULT_APPLICATIONS); 370 assert roleManager != null; 371 roleManager.setDefaultApplication( 372 RoleManager.ROLE_WALLET, 373 packageName, 374 0, 375 MoreExecutors.directExecutor(), 376 aBoolean -> { 377 result.set(aBoolean); 378 countDownLatch.countDown(); 379 }); 380 countDownLatch.await(3000, TimeUnit.MILLISECONDS); 381 } catch (InterruptedException e) { 382 throw new RuntimeException(e); 383 } finally { 384 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation() 385 .getUiAutomation() 386 .dropShellPermissionIdentity(); 387 } 388 return result.get(); 389 } 390 391 /** Disables secure NFC so that NFC works with screen off */ disableSecureNfc(NfcAdapter adapter)392 public static boolean disableSecureNfc(NfcAdapter adapter) { 393 boolean res = false; 394 try { 395 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation() 396 .getUiAutomation() 397 .adoptShellPermissionIdentity(WRITE_SECURE_SETTINGS); 398 res = adapter.enableSecureNfc(false); 399 } finally { 400 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation() 401 .getUiAutomation() 402 .dropShellPermissionIdentity(); 403 } 404 return res; 405 } 406 } 407