1 /* 2 * Copyright (C) 2019 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.permission.cts; 18 19 import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION; 20 import static android.Manifest.permission.ACCESS_COARSE_LOCATION; 21 import static android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY; 22 import static android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS; 23 import static android.Manifest.permission.MANAGE_APP_OPS_MODES; 24 import static android.Manifest.permission.PACKAGE_USAGE_STATS; 25 import static android.app.AppOpsManager.MODE_ALLOWED; 26 import static android.app.AppOpsManager.MODE_FOREGROUND; 27 import static android.app.AppOpsManager.MODE_IGNORED; 28 import static android.app.AppOpsManager.OPSTR_GET_USAGE_STATS; 29 import static android.app.AppOpsManager.permissionToOp; 30 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; 31 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE; 32 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED; 33 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED; 34 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET; 35 import static android.content.pm.PackageManager.GET_PERMISSIONS; 36 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 37 import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; 38 import static android.permission.cts.TestUtils.awaitJobUntilRequestedState; 39 40 import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity; 41 import static com.android.compatibility.common.util.SystemUtil.runShellCommand; 42 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; 43 import static com.android.compatibility.common.util.SystemUtil.waitForBroadcasts; 44 45 import android.app.AppOpsManager; 46 import android.app.UiAutomation; 47 import android.content.Context; 48 import android.content.Intent; 49 import android.content.pm.PackageInfo; 50 import android.content.pm.PermissionInfo; 51 import android.content.pm.ResolveInfo; 52 import android.os.Build; 53 import android.os.Process; 54 import android.os.UserHandle; 55 import android.util.Log; 56 57 import androidx.annotation.NonNull; 58 import androidx.test.platform.app.InstrumentationRegistry; 59 60 import com.android.modules.utils.build.SdkLevel; 61 62 import java.util.ArrayList; 63 import java.util.Arrays; 64 import java.util.List; 65 66 /** 67 * Common utils for permission tests 68 */ 69 public class PermissionUtils { 70 private static final int TESTED_FLAGS = FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED 71 | FLAG_PERMISSION_REVOKE_ON_UPGRADE | FLAG_PERMISSION_REVIEW_REQUIRED 72 | FLAG_PERMISSION_REVOKE_WHEN_REQUESTED; 73 74 private static final String LOG_TAG = PermissionUtils.class.getSimpleName(); 75 private static final Context sContext = 76 InstrumentationRegistry.getInstrumentation().getTargetContext(); 77 private static final UiAutomation sUiAutomation = 78 InstrumentationRegistry.getInstrumentation().getUiAutomation(); 79 PermissionUtils()80 private PermissionUtils() { 81 // this class should never be instantiated 82 } 83 84 /** 85 * Get the state of an app-op. 86 * 87 * @param packageName The package the app-op belongs to 88 * @param permission The permission the app-op belongs to 89 * 90 * @return The mode the op is on 91 */ getAppOp(@onNull String packageName, @NonNull String permission)92 public static int getAppOp(@NonNull String packageName, @NonNull String permission) 93 throws Exception { 94 return sContext.getSystemService(AppOpsManager.class).unsafeCheckOpRaw( 95 permissionToOp(permission), 96 sContext.getPackageManager().getPackageUid(packageName, 0), packageName); 97 } 98 99 /** 100 * Install an APK. 101 * 102 * @param apkFile The apk to install 103 */ install(@onNull String apkFile)104 public static void install(@NonNull String apkFile) { 105 final int sdkVersion = Build.VERSION.SDK_INT 106 + (Build.VERSION.RELEASE_OR_CODENAME.equals("REL") ? 0 : 1); 107 boolean forceQueryable = sdkVersion > Build.VERSION_CODES.Q; 108 runShellCommand("pm install -r --force-sdk " 109 + (SdkLevel.isAtLeastU() ? "--bypass-low-target-sdk-block " : "") 110 + (forceQueryable ? "--force-queryable " : "") 111 + apkFile); 112 } 113 114 /** 115 * Uninstall a package. 116 * 117 * @param packageName Name of package to be uninstalled 118 */ uninstallApp(@onNull String packageName)119 public static void uninstallApp(@NonNull String packageName) { 120 runShellCommand("pm uninstall " + packageName); 121 } 122 123 /** 124 * Set a new state for an app-op (using the permission-name) 125 * 126 * @param packageName The package the app-op belongs to 127 * @param permission The permission the app-op belongs to 128 * @param mode The new mode 129 */ setAppOp(@onNull String packageName, @NonNull String permission, int mode)130 public static void setAppOp(@NonNull String packageName, @NonNull String permission, int mode) { 131 setAppOpByName(packageName, permissionToOp(permission), mode); 132 } 133 134 /** 135 * Set a new state for an app-op (using the app-op-name) 136 * 137 * @param packageName The package the app-op belongs to 138 * @param op The name of the op 139 * @param mode The new mode 140 */ setAppOpByName(@onNull String packageName, @NonNull String op, int mode)141 public static void setAppOpByName(@NonNull String packageName, @NonNull String op, int mode) { 142 runWithShellPermissionIdentity( 143 () -> sContext.getSystemService(AppOpsManager.class).setUidMode(op, 144 sContext.getPackageManager().getPackageUid(packageName, 0), mode), 145 MANAGE_APP_OPS_MODES); 146 } 147 148 /** 149 * Checks a permission. Does <u>not</u> check the appOp. 150 * 151 * <p>Users should use {@link #isGranted} instead. 152 * 153 * @param packageName The package that might have the permission granted 154 * @param permission The permission that might be granted 155 * 156 * @return {@code true} iff the permission is granted 157 */ isPermissionGranted(@onNull String packageName, @NonNull String permission)158 public static boolean isPermissionGranted(@NonNull String packageName, 159 @NonNull String permission) throws Exception { 160 return sContext.checkPermission(permission, Process.myPid(), 161 sContext.getPackageManager().getPackageUid(packageName, 0)) 162 == PERMISSION_GRANTED; 163 } 164 165 /** 166 * Checks if a permission is granted for a package. 167 * 168 * <p>This correctly handles pre-M apps by checking the app-ops instead. 169 * <p>This also correctly handles the location background permission, but does not handle any 170 * other background permission 171 * 172 * @param packageName The package that might have the permission granted 173 * @param permission The permission that might be granted 174 * 175 * @return {@code true} iff the permission is granted 176 */ isGranted(@onNull String packageName, @NonNull String permission)177 public static boolean isGranted(@NonNull String packageName, @NonNull String permission) 178 throws Exception { 179 if (!isPermissionGranted(packageName, permission)) { 180 return false; 181 } 182 183 if (permission.equals(ACCESS_BACKGROUND_LOCATION)) { 184 // The app-op for background location is encoded into the mode of the foreground 185 // location 186 return getAppOp(packageName, ACCESS_COARSE_LOCATION) == MODE_ALLOWED; 187 } else { 188 int mode = getAppOp(packageName, permission); 189 return mode == MODE_ALLOWED || mode == MODE_FOREGROUND; 190 } 191 } 192 193 /** 194 * Grant a permission to an app. 195 * 196 * <p>This correctly handles pre-M apps by setting the app-ops. 197 * <p>This also correctly handles the location background permission, but does not handle any 198 * other background permission 199 * 200 * @param packageName The app that should have the permission granted 201 * @param permission The permission to grant 202 */ grantPermission(@onNull String packageName, @NonNull String permission)203 public static void grantPermission(@NonNull String packageName, @NonNull String permission) 204 throws Exception { 205 sUiAutomation.grantRuntimePermission(packageName, permission); 206 207 if (permission.equals(ACCESS_BACKGROUND_LOCATION)) { 208 // The app-op for background location is encoded into the mode of the foreground 209 // location 210 if (isPermissionGranted(packageName, ACCESS_COARSE_LOCATION)) { 211 setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_ALLOWED); 212 } else { 213 setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_FOREGROUND); 214 } 215 } else if (permission.equals(ACCESS_COARSE_LOCATION)) { 216 // The app-op for location depends on the state of the bg location 217 if (isPermissionGranted(packageName, ACCESS_BACKGROUND_LOCATION)) { 218 setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_ALLOWED); 219 } else { 220 setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_FOREGROUND); 221 } 222 } else if (permission.equals(PACKAGE_USAGE_STATS)) { 223 setAppOpByName(packageName, OPSTR_GET_USAGE_STATS, MODE_ALLOWED); 224 } else if (permissionToOp(permission) != null) { 225 setAppOp(packageName, permission, MODE_ALLOWED); 226 } 227 } 228 229 /** 230 * Revoke a permission from an app. 231 * 232 * <p>This correctly handles pre-M apps by setting the app-ops. 233 * <p>This also correctly handles the location background permission, but does not handle any 234 * other background permission 235 * 236 * @param packageName The app that should have the permission revoked 237 * @param permission The permission to revoke 238 */ revokePermission(@onNull String packageName, @NonNull String permission)239 public static void revokePermission(@NonNull String packageName, @NonNull String permission) 240 throws Exception { 241 sUiAutomation.revokeRuntimePermission(packageName, permission); 242 243 if (permission.equals(ACCESS_BACKGROUND_LOCATION)) { 244 // The app-op for background location is encoded into the mode of the foreground 245 // location 246 if (isGranted(packageName, ACCESS_COARSE_LOCATION)) { 247 setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_FOREGROUND); 248 } 249 } else if (permission.equals(PACKAGE_USAGE_STATS)) { 250 setAppOpByName(packageName, OPSTR_GET_USAGE_STATS, MODE_IGNORED); 251 } else if (permissionToOp(permission) != null) { 252 setAppOp(packageName, permission, MODE_IGNORED); 253 } 254 } 255 256 /** 257 * Clear permission state (not app-op state) of package. 258 * 259 * @param packageName Package to clear 260 */ clearAppState(@onNull String packageName)261 public static void clearAppState(@NonNull String packageName) { 262 runShellCommand("pm clear --user current " + packageName); 263 } 264 265 /** 266 * Get all the flags of a permission. 267 * 268 * @param packageName Package the permission belongs to 269 * @param permission Name of the permission 270 * 271 * @return Permission flags 272 */ getAllPermissionFlags(@onNull String packageName, @NonNull String permission)273 public static int getAllPermissionFlags(@NonNull String packageName, 274 @NonNull String permission) { 275 try { 276 return callWithShellPermissionIdentity( 277 () -> sContext.getPackageManager().getPermissionFlags(permission, packageName, 278 UserHandle.getUserHandleForUid(Process.myUid())), 279 GRANT_RUNTIME_PERMISSIONS); 280 } catch (Exception e) { 281 throw new IllegalStateException(e); 282 } 283 } 284 285 /** 286 * Get the flags of a permission. 287 * 288 * @param packageName Package the permission belongs to 289 * @param permission Name of the permission 290 * 291 * @return Permission flags 292 */ getPermissionFlags(@onNull String packageName, @NonNull String permission)293 public static int getPermissionFlags(@NonNull String packageName, @NonNull String permission) { 294 return getAllPermissionFlags(packageName, permission) & TESTED_FLAGS; 295 } 296 297 /** 298 * Set the flags of a permission. 299 * 300 * @param packageName Package the permission belongs to 301 * @param permission Name of the permission 302 * @param mask Mask of permissions to set 303 * @param flags Permissions to set 304 */ setPermissionFlags(@onNull String packageName, @NonNull String permission, int mask, int flags)305 public static void setPermissionFlags(@NonNull String packageName, @NonNull String permission, 306 int mask, int flags) { 307 runWithShellPermissionIdentity( 308 () -> sContext.getPackageManager().updatePermissionFlags(permission, packageName, 309 mask, flags, UserHandle.getUserHandleForUid(Process.myUid())), 310 GRANT_RUNTIME_PERMISSIONS, ADJUST_RUNTIME_PERMISSIONS_POLICY); 311 } 312 313 /** 314 * Get all permissions an app requests. This includes the split permissions. 315 * 316 * @param packageName The package that requests the permissions. 317 * 318 * @return The permissions requested by the app 319 */ getPermissions(@onNull String packageName)320 public static @NonNull List<String> getPermissions(@NonNull String packageName) 321 throws Exception { 322 PackageInfo appInfo = sContext.getPackageManager().getPackageInfo(packageName, 323 GET_PERMISSIONS); 324 325 return Arrays.asList(appInfo.requestedPermissions); 326 } 327 328 /** 329 * Get all runtime permissions that an app requests. This includes the split permissions. 330 * 331 * @param packageName The package that requests the permissions. 332 * 333 * @return The runtime permissions requested by the app 334 */ getRuntimePermissions(@onNull String packageName)335 public static @NonNull List<String> getRuntimePermissions(@NonNull String packageName) 336 throws Exception { 337 ArrayList<String> runtimePermissions = new ArrayList<>(); 338 339 for (String perm : getPermissions(packageName)) { 340 PermissionInfo info = sContext.getPackageManager().getPermissionInfo(perm, 0); 341 if ((info.getProtection() & PROTECTION_DANGEROUS) != 0) { 342 runtimePermissions.add(perm); 343 } 344 } 345 346 return runtimePermissions; 347 } 348 349 /** 350 * Reset permission controller state & re-schedule the job. 351 */ resetPermissionControllerJob(@onNull UiAutomation automation, @NonNull String packageName, int jobId, long timeout, @NonNull String intentAction, @NonNull String onBootReceiver)352 public static void resetPermissionControllerJob(@NonNull UiAutomation automation, 353 @NonNull String packageName, int jobId, long timeout, @NonNull String intentAction, 354 @NonNull String onBootReceiver) throws Exception { 355 clearAppState(packageName); 356 awaitJobUntilRequestedState(packageName, jobId, timeout, automation, "unknown"); 357 scheduleJob(automation, packageName, jobId, timeout, intentAction, onBootReceiver); 358 359 runShellCommand("cmd jobscheduler reset-execution-quota -u " 360 + Process.myUserHandle().getIdentifier() + " " + packageName); 361 runShellCommand("cmd jobscheduler reset-schedule-quota"); 362 } 363 364 /** 365 * schedules a job for the privacy signal in Permission Controller 366 */ scheduleJob(@onNull UiAutomation automation, @NonNull String packageName, int jobId, long timeout, @NonNull String intentAction, @NonNull String broadcastReceiver)367 public static void scheduleJob(@NonNull UiAutomation automation, 368 @NonNull String packageName, int jobId, long timeout, @NonNull String intentAction, 369 @NonNull String broadcastReceiver) throws Exception { 370 long startTime = System.currentTimeMillis(); 371 String jobStatus = ""; 372 373 while ((System.currentTimeMillis() - startTime) < timeout 374 && !jobStatus.contains("waiting")) { 375 simulateReboot(packageName, intentAction, broadcastReceiver); 376 String cmd = 377 "cmd jobscheduler get-job-state -u " + Process.myUserHandle().getIdentifier() 378 + " " + packageName + " " + jobId; 379 jobStatus = runShellCommand(automation, cmd).trim(); 380 Log.v(LOG_TAG, "Job: " + jobId + ", job status " + jobStatus); 381 } 382 if (!jobStatus.contains("waiting")) { 383 throw new IllegalStateException("The job didn't get scheduled in time."); 384 } 385 } 386 simulateReboot(@onNull String packageName, @NonNull String intentAction, @NonNull String broadcastReceiver)387 private static void simulateReboot(@NonNull String packageName, @NonNull String intentAction, 388 @NonNull String broadcastReceiver) { 389 Intent jobSetupReceiverIntent = new Intent(intentAction); 390 jobSetupReceiverIntent.setPackage(packageName); 391 jobSetupReceiverIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); 392 393 // Query for the setup broadcast receiver 394 List<ResolveInfo> resolveInfos = 395 sContext.getPackageManager().queryBroadcastReceivers(jobSetupReceiverIntent, 0); 396 397 if (resolveInfos.size() > 0) { 398 sContext.sendBroadcast(jobSetupReceiverIntent); 399 } else { 400 Intent intent = new Intent(); 401 intent.setClassName(packageName, broadcastReceiver); 402 intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); 403 intent.setPackage(packageName); 404 sContext.sendBroadcast(intent); 405 } 406 waitForBroadcasts(); 407 } 408 } 409