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