1 /*
2  * Copyright (C) 2024 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 android.nfc.cts;
18 
19 import static android.Manifest.permission.MANAGE_DEFAULT_APPLICATIONS;
20 import static android.Manifest.permission.MANAGE_ROLE_HOLDERS;
21 import static android.Manifest.permission.OBSERVE_ROLE_HOLDERS;
22 
23 import static org.junit.Assume.assumeFalse;
24 
25 import android.app.role.OnRoleHoldersChangedListener;
26 import android.app.role.RoleManager;
27 import android.content.ComponentName;
28 import android.content.Context;
29 import android.content.res.Resources;
30 import android.os.UserManager;
31 import android.text.TextUtils;
32 
33 import com.google.common.util.concurrent.MoreExecutors;
34 
35 import org.junit.Assert;
36 
37 import java.util.Arrays;
38 import java.util.List;
39 import java.util.concurrent.CountDownLatch;
40 import java.util.concurrent.TimeUnit;
41 import java.util.concurrent.atomic.AtomicReference;
42 
43 
44 public final class WalletRoleTestUtils {
45 
WalletRoleTestUtils()46     private WalletRoleTestUtils() {}
47 
48     static final String CTS_PACKAGE_NAME = "android.nfc.cts";
49     static final String WALLET_HOLDER_PACKAGE_NAME = "com.android.test.walletroleholder";
50     static final String WALLET_HOLDER_SERVICE_DESC = "Wallet Role CTS Nfc Test Service";
51     static final String NFC_FOREGROUND_PACKAGE_NAME = "com.android.test.foregroundnfc";
52     static final String NON_PAYMENT_NFC_PACKAGE_NAME = "com.android.test.nonpaymentnfc";
53     static final String PAYMENT_AID_1 = "A000000004101012";
54     static final String PAYMENT_AID_2 = "A000000004101018";
55     static final String NON_PAYMENT_AID_1 = "F053414950454D";
56 
57     private static final String WALLET_OVERLAY_CONFIG = "config_defaultWallet";
58     private static final String CERTIFICATE_SEPARATOR = ":";
59 
60     static final List<String> WALLET_HOLDER_AIDS = Arrays.asList("A000000004101011",
61             "A000000004101012",
62             "A000000004101013",
63             "A000000004101018");
64 
getWalletRoleHolderService()65     static ComponentName getWalletRoleHolderService() {
66         return new ComponentName(WALLET_HOLDER_PACKAGE_NAME,
67                 "com.android.test.walletroleholder.WalletRoleHolderApduService");
68     }
69 
getWalletRoleHolderXService()70     static ComponentName getWalletRoleHolderXService() {
71         return new ComponentName(WALLET_HOLDER_PACKAGE_NAME,
72                 "com.android.test.walletroleholder.XWalletRoleHolderApduService");
73     }
74 
getForegroundService()75     static ComponentName getForegroundService() {
76         return new ComponentName(NFC_FOREGROUND_PACKAGE_NAME,
77                 "com.android.test.foregroundnfc.ForegroundApduService");
78     }
79 
getNonPaymentService()80     static ComponentName getNonPaymentService() {
81         return new ComponentName(NON_PAYMENT_NFC_PACKAGE_NAME,
82                 "com.android.test.nonpaymentnfc.NonPaymentApduService");
83     }
84 
getWalletRoleHolderActivity()85     static ComponentName getWalletRoleHolderActivity() {
86         return new ComponentName(WALLET_HOLDER_PACKAGE_NAME,
87                 "com.android.test.walletroleholder.WalletRoleHolderForegroundActivity");
88     }
89 
setDefaultWalletRoleHolder(Context context, String packageName)90     static boolean setDefaultWalletRoleHolder(Context context, String packageName) {
91         RoleManager roleManager = context.getSystemService(RoleManager.class);
92         CountDownLatch countDownLatch = new CountDownLatch(1);
93         AtomicReference<Boolean> result = new AtomicReference<>(false);
94         try {
95             roleManager.setDefaultApplication(RoleManager.ROLE_WALLET,
96                     packageName, 0,
97                     MoreExecutors.directExecutor(), aBoolean -> {
98                         result.set(aBoolean);
99                         countDownLatch.countDown();
100                     });
101             countDownLatch.await(3000, TimeUnit.MILLISECONDS);
102         } catch (InterruptedException e) {
103             return false;
104         }
105         return result.get();
106     }
107 
removeRoleHolder(Context context, String currentHolder)108     static boolean removeRoleHolder(Context context, String currentHolder) {
109         RoleManager roleManager = context.getSystemService(RoleManager.class);
110         CountDownLatch countDownLatch = new CountDownLatch(1);
111         AtomicReference<Boolean> result = new AtomicReference<>(false);
112         try {
113             roleManager.removeRoleHolderAsUser(RoleManager.ROLE_WALLET, currentHolder, 0,
114                     context.getUser(), MoreExecutors.directExecutor(), aBoolean -> {
115                         result.set(aBoolean);
116                         countDownLatch.countDown();
117                     });
118             countDownLatch.await(3000, TimeUnit.MILLISECONDS);
119         } catch (InterruptedException e) {
120             return false;
121         }
122         return result.get();
123     }
124 
setDefaultWalletRoleHolder(Context context)125     static boolean setDefaultWalletRoleHolder(Context context) {
126         return setDefaultWalletRoleHolder(context, "android.nfc.cts");
127     }
128 
getDefaultWalletRoleHolder(Context context)129     static String getDefaultWalletRoleHolder(Context context) {
130         try {
131             androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
132                     .getUiAutomation().adoptShellPermissionIdentity(MANAGE_DEFAULT_APPLICATIONS);
133             RoleManager roleManager = context.getSystemService(RoleManager.class);
134             return roleManager.getDefaultApplication(RoleManager.ROLE_WALLET);
135         } finally {
136             androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
137                     .getUiAutomation().dropShellPermissionIdentity();
138         }
139     }
140 
runWithRole(Context context, String roleHolder, Runnable runnable)141     static void runWithRole(Context context, String roleHolder, Runnable runnable) {
142         final UserManager userManager = context.getSystemService(UserManager.class);
143         assumeFalse(userManager.isHeadlessSystemUserMode());
144         try {
145             runWithRoleNone(context, () -> {}); //Remove the role holder first to trigger callbacks
146             RoleManager roleManager = context.getSystemService(RoleManager.class);
147             CountDownLatch countDownLatch = new CountDownLatch(1);
148             OnRoleHoldersChangedListener onRoleHoldersChangedListener = (roleName, user) -> {
149                 if (roleName.equals(RoleManager.ROLE_WALLET)) {
150                     try {
151                         // Wait a second to make sure all other callbacks are also fired on
152                         // their respective executors.
153                         Thread.sleep(1000);
154                     } catch (InterruptedException e) {
155                         throw new RuntimeException(e);
156                     }
157                     countDownLatch.countDown();
158                 }
159             };
160             androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
161                     .getUiAutomation().adoptShellPermissionIdentity(OBSERVE_ROLE_HOLDERS);
162             roleManager.addOnRoleHoldersChangedListenerAsUser(context.getMainExecutor(),
163                     onRoleHoldersChangedListener, context.getUser());
164             androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
165                     .getUiAutomation().adoptShellPermissionIdentity(MANAGE_DEFAULT_APPLICATIONS);
166             Assert.assertTrue(setDefaultWalletRoleHolder(context, roleHolder));
167             countDownLatch.await(4000, TimeUnit.MILLISECONDS);
168             androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
169                     .getUiAutomation().adoptShellPermissionIdentity(OBSERVE_ROLE_HOLDERS);
170             roleManager.removeOnRoleHoldersChangedListenerAsUser(onRoleHoldersChangedListener,
171                     context.getUser());
172             runnable.run();
173         } catch (InterruptedException e) {
174             throw new RuntimeException(e);
175         } finally {
176             runWithRoleNone(context, () -> {}); //Remove the role holder first to trigger callbacks
177             androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
178                     .getUiAutomation().dropShellPermissionIdentity();
179         }
180     }
181 
canAssignRoleToPackage(Context context, String packageName)182     static boolean canAssignRoleToPackage(Context context, String packageName) {
183         String previousHolder = getDefaultWalletRoleHolder(context);
184         try {
185             androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
186                     .getUiAutomation().adoptShellPermissionIdentity(MANAGE_DEFAULT_APPLICATIONS);
187             boolean canSet = setDefaultWalletRoleHolder(context, packageName);
188             if (canSet && previousHolder != null) {
189                 setDefaultWalletRoleHolder(context, previousHolder);
190             }
191             return canSet;
192         } finally {
193             androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
194                     .getUiAutomation().dropShellPermissionIdentity();
195         }
196     }
197 
runWithRoleNone(Context context, Runnable runnable)198     static void runWithRoleNone(Context context, Runnable runnable) {
199         try {
200             String currentHolder = getDefaultWalletRoleHolder(context);
201             RoleManager roleManager = context.getSystemService(RoleManager.class);
202             CountDownLatch countDownLatch = new CountDownLatch(1);
203             OnRoleHoldersChangedListener onRoleHoldersChangedListener = (roleName, user) -> {
204                 if (roleName.equals(RoleManager.ROLE_WALLET)) {
205                     try {
206                         // Wait a second to make sure all other callbacks are also fired on
207                         // their respective executors.
208                         Thread.sleep(1000);
209                     } catch (InterruptedException e) {
210                         throw new RuntimeException(e);
211                     }
212                     countDownLatch.countDown();
213                 }
214             };
215             androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
216                     .getUiAutomation().adoptShellPermissionIdentity(OBSERVE_ROLE_HOLDERS);
217             roleManager.addOnRoleHoldersChangedListenerAsUser(context.getMainExecutor(),
218                     onRoleHoldersChangedListener, context.getUser());
219             androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
220                     .getUiAutomation().adoptShellPermissionIdentity(MANAGE_ROLE_HOLDERS);
221             if (currentHolder != null) {
222                 roleManager.setRoleFallbackEnabled(RoleManager.ROLE_WALLET, false);
223                 Assert.assertTrue(removeRoleHolder(context, currentHolder));
224                 countDownLatch.await(4000, TimeUnit.MILLISECONDS);
225                 roleManager.setRoleFallbackEnabled(RoleManager.ROLE_WALLET, true);
226             }
227             androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
228                     .getUiAutomation().adoptShellPermissionIdentity(OBSERVE_ROLE_HOLDERS);
229             roleManager.removeOnRoleHoldersChangedListenerAsUser(onRoleHoldersChangedListener,
230                     context.getUser());
231             runnable.run();
232         } catch (InterruptedException e) {
233             throw new RuntimeException(e);
234         } finally {
235             androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
236                     .getUiAutomation().dropShellPermissionIdentity();
237         }
238     }
239 
clearRoleHolders(Context context)240     static void clearRoleHolders(Context context) {
241         try {
242             androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
243                     .getUiAutomation().adoptShellPermissionIdentity(MANAGE_ROLE_HOLDERS);
244             RoleManager roleManager = context.getSystemService(RoleManager.class);
245             CountDownLatch countDownLatch = new CountDownLatch(1);
246             AtomicReference<Boolean> result = new AtomicReference<>(false);
247             roleManager.clearRoleHoldersAsUser(RoleManager.ROLE_WALLET, 0,
248                     context.getUser(), MoreExecutors.directExecutor(), aBoolean -> {
249                         try {
250                             // Wait a second to make sure all other callbacks are also fired on
251                             // their respective executors.
252                             Thread.sleep(1000);
253                         } catch (InterruptedException e) {
254                             throw new RuntimeException(e);
255                         }
256                         result.set(aBoolean);
257                         countDownLatch.countDown();
258                     });
259             countDownLatch.await(4000, TimeUnit.MILLISECONDS);
260         } catch (Exception e) {
261             throw new RuntimeException(e);
262         } finally {
263             androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
264                     .getUiAutomation().dropShellPermissionIdentity();
265         }
266     }
267 
getOverLayDefaultHolder(Context context)268     static String getOverLayDefaultHolder(Context context) {
269         Resources resources = context.getResources();
270         int resourceId = resources.getIdentifier(WALLET_OVERLAY_CONFIG,
271                 "string", "android");
272         if (resourceId == 0) {
273             return null;
274         }
275         String defaultHolders;
276         try {
277             defaultHolders = resources.getString(resourceId);
278         } catch (Resources.NotFoundException e) {
279             return null;
280         }
281         if (TextUtils.isEmpty(defaultHolders)) {
282             return null;
283         }
284         int certificateSeparatorIndex = defaultHolders.indexOf(CERTIFICATE_SEPARATOR);
285         if (certificateSeparatorIndex != -1) {
286             return defaultHolders.substring(0, certificateSeparatorIndex);
287         } else {
288             return defaultHolders;
289         }
290     }
291 }
292