• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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