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