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