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