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