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