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.PACKAGE_USAGE_STATS; 22 import static android.app.AppOpsManager.MODE_ALLOWED; 23 import static android.app.AppOpsManager.MODE_FOREGROUND; 24 import static android.app.AppOpsManager.MODE_IGNORED; 25 import static android.app.AppOpsManager.OPSTR_GET_USAGE_STATS; 26 import static android.app.AppOpsManager.permissionToOp; 27 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; 28 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE; 29 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED; 30 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED; 31 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET; 32 import static android.content.pm.PackageManager.GET_PERMISSIONS; 33 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 34 import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; 35 36 import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity; 37 import static com.android.compatibility.common.util.SystemUtil.runShellCommand; 38 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; 39 40 import android.app.AppOpsManager; 41 import android.app.UiAutomation; 42 import android.content.Context; 43 import android.content.pm.PackageInfo; 44 import android.content.pm.PermissionInfo; 45 import android.os.Build; 46 import android.os.Process; 47 import android.os.UserHandle; 48 49 import androidx.annotation.NonNull; 50 import androidx.test.platform.app.InstrumentationRegistry; 51 52 import java.util.ArrayList; 53 import java.util.Arrays; 54 import java.util.List; 55 56 /** 57 * Common utils for permission tests 58 */ 59 public class PermissionUtils { 60 private static String GRANT_RUNTIME_PERMISSIONS = "android.permission.GRANT_RUNTIME_PERMISSIONS"; 61 private static String MANAGE_APP_OPS_MODES = "android.permission.MANAGE_APP_OPS_MODES"; 62 63 private static final int TESTED_FLAGS = FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED 64 | FLAG_PERMISSION_REVOKE_ON_UPGRADE | FLAG_PERMISSION_REVIEW_REQUIRED 65 | FLAG_PERMISSION_REVOKE_WHEN_REQUESTED; 66 67 private static final Context sContext = 68 InstrumentationRegistry.getInstrumentation().getTargetContext(); 69 private static final UiAutomation sUiAutomation = 70 InstrumentationRegistry.getInstrumentation().getUiAutomation(); 71 PermissionUtils()72 private PermissionUtils() { 73 // this class should never be instantiated 74 } 75 76 /** 77 * Get the state of an app-op. 78 * 79 * @param packageName The package the app-op belongs to 80 * @param permission The permission the app-op belongs to 81 * 82 * @return The mode the op is on 83 */ getAppOp(@onNull String packageName, @NonNull String permission)84 public static int getAppOp(@NonNull String packageName, @NonNull String permission) 85 throws Exception { 86 return sContext.getSystemService(AppOpsManager.class).unsafeCheckOpRaw( 87 permissionToOp(permission), 88 sContext.getPackageManager().getPackageUid(packageName, 0), packageName); 89 } 90 91 /** 92 * Install an APK. 93 * 94 * @param apkFile The apk to install 95 */ install(@onNull String apkFile)96 public static void install(@NonNull String apkFile) { 97 final int sdkVersion = Build.VERSION.SDK_INT 98 + (Build.VERSION.RELEASE_OR_CODENAME.equals("REL") ? 0 : 1); 99 boolean forceQueryable = sdkVersion > Build.VERSION_CODES.Q; 100 runShellCommand("pm install -r --force-sdk " 101 + (forceQueryable ? "--force-queryable " : "") 102 + apkFile); 103 } 104 105 /** 106 * Uninstall a package. 107 * 108 * @param packageName Name of package to be uninstalled 109 */ uninstallApp(@onNull String packageName)110 public static void uninstallApp(@NonNull String packageName) { 111 runShellCommand("pm uninstall " + packageName); 112 } 113 114 /** 115 * Set a new state for an app-op (using the permission-name) 116 * 117 * @param packageName The package the app-op belongs to 118 * @param permission The permission the app-op belongs to 119 * @param mode The new mode 120 */ setAppOp(@onNull String packageName, @NonNull String permission, int mode)121 public static void setAppOp(@NonNull String packageName, @NonNull String permission, int mode) { 122 setAppOpByName(packageName, permissionToOp(permission), mode); 123 } 124 125 /** 126 * Set a new state for an app-op (using the app-op-name) 127 * 128 * @param packageName The package the app-op belongs to 129 * @param op The name of the op 130 * @param mode The new mode 131 */ setAppOpByName(@onNull String packageName, @NonNull String op, int mode)132 public static void setAppOpByName(@NonNull String packageName, @NonNull String op, int mode) { 133 runWithShellPermissionIdentity( 134 () -> sContext.getSystemService(AppOpsManager.class).setUidMode(op, 135 sContext.getPackageManager().getPackageUid(packageName, 0), mode), 136 MANAGE_APP_OPS_MODES); 137 } 138 139 /** 140 * Checks a permission. Does <u>not</u> check the appOp. 141 * 142 * <p>Users should use {@link #isGranted} instead. 143 * 144 * @param packageName The package that might have the permission granted 145 * @param permission The permission that might be granted 146 * 147 * @return {@code true} iff the permission is granted 148 */ isPermissionGranted(@onNull String packageName, @NonNull String permission)149 public static boolean isPermissionGranted(@NonNull String packageName, 150 @NonNull String permission) throws Exception { 151 return sContext.checkPermission(permission, Process.myPid(), 152 sContext.getPackageManager().getPackageUid(packageName, 0)) 153 == PERMISSION_GRANTED; 154 } 155 156 /** 157 * Checks if a permission is granted for a package. 158 * 159 * <p>This correctly handles pre-M apps by checking the app-ops instead. 160 * <p>This also correctly handles the location background permission, but does not handle any 161 * other background permission 162 * 163 * @param packageName The package that might have the permission granted 164 * @param permission The permission that might be granted 165 * 166 * @return {@code true} iff the permission is granted 167 */ isGranted(@onNull String packageName, @NonNull String permission)168 public static boolean isGranted(@NonNull String packageName, @NonNull String permission) 169 throws Exception { 170 if (!isPermissionGranted(packageName, permission)) { 171 return false; 172 } 173 174 if (permission.equals(ACCESS_BACKGROUND_LOCATION)) { 175 // The app-op for background location is encoded into the mode of the foreground 176 // location 177 return getAppOp(packageName, ACCESS_COARSE_LOCATION) == MODE_ALLOWED; 178 } else { 179 int mode = getAppOp(packageName, permission); 180 return mode == MODE_ALLOWED || mode == MODE_FOREGROUND; 181 } 182 } 183 184 /** 185 * Grant a permission to an app. 186 * 187 * <p>This correctly handles pre-M apps by setting the app-ops. 188 * <p>This also correctly handles the location background permission, but does not handle any 189 * other background permission 190 * 191 * @param packageName The app that should have the permission granted 192 * @param permission The permission to grant 193 */ grantPermission(@onNull String packageName, @NonNull String permission)194 public static void grantPermission(@NonNull String packageName, @NonNull String permission) 195 throws Exception { 196 sUiAutomation.grantRuntimePermission(packageName, permission); 197 198 if (permission.equals(ACCESS_BACKGROUND_LOCATION)) { 199 // The app-op for background location is encoded into the mode of the foreground 200 // location 201 if (isPermissionGranted(packageName, ACCESS_COARSE_LOCATION)) { 202 setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_ALLOWED); 203 } else { 204 setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_FOREGROUND); 205 } 206 } else if (permission.equals(ACCESS_COARSE_LOCATION)) { 207 // The app-op for location depends on the state of the bg location 208 if (isPermissionGranted(packageName, ACCESS_BACKGROUND_LOCATION)) { 209 setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_ALLOWED); 210 } else { 211 setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_FOREGROUND); 212 } 213 } else if (permission.equals(PACKAGE_USAGE_STATS)) { 214 setAppOpByName(packageName, OPSTR_GET_USAGE_STATS, MODE_ALLOWED); 215 } else if (permissionToOp(permission) != null) { 216 setAppOp(packageName, permission, MODE_ALLOWED); 217 } 218 } 219 220 /** 221 * Revoke a permission from an app. 222 * 223 * <p>This correctly handles pre-M apps by setting the app-ops. 224 * <p>This also correctly handles the location background permission, but does not handle any 225 * other background permission 226 * 227 * @param packageName The app that should have the permission revoked 228 * @param permission The permission to revoke 229 */ revokePermission(@onNull String packageName, @NonNull String permission)230 public static void revokePermission(@NonNull String packageName, @NonNull String permission) 231 throws Exception { 232 sUiAutomation.revokeRuntimePermission(packageName, permission); 233 234 if (permission.equals(ACCESS_BACKGROUND_LOCATION)) { 235 // The app-op for background location is encoded into the mode of the foreground 236 // location 237 if (isGranted(packageName, ACCESS_COARSE_LOCATION)) { 238 setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_FOREGROUND); 239 } 240 } else if (permission.equals(PACKAGE_USAGE_STATS)) { 241 setAppOpByName(packageName, OPSTR_GET_USAGE_STATS, MODE_IGNORED); 242 } else if (permissionToOp(permission) != null) { 243 setAppOp(packageName, permission, MODE_IGNORED); 244 } 245 } 246 247 /** 248 * Clear permission state (not app-op state) of package. 249 * 250 * @param packageName Package to clear 251 */ clearAppState(@onNull String packageName)252 public static void clearAppState(@NonNull String packageName) { 253 runShellCommand("pm clear --user current " + packageName); 254 } 255 256 /** 257 * Get the flags of a permission. 258 * 259 * @param packageName Package the permission belongs to 260 * @param permission Name of the permission 261 * 262 * @return Permission flags 263 */ getPermissionFlags(@onNull String packageName, @NonNull String permission)264 public static int getPermissionFlags(@NonNull String packageName, @NonNull String permission) { 265 try { 266 return callWithShellPermissionIdentity( 267 () -> sContext.getPackageManager().getPermissionFlags(permission, packageName, 268 UserHandle.getUserHandleForUid(Process.myUid())) & TESTED_FLAGS, 269 GRANT_RUNTIME_PERMISSIONS); 270 } catch (Exception e) { 271 throw new IllegalStateException(e); 272 } 273 } 274 275 /** 276 * Set the flags of a permission. 277 * 278 * @param packageName Package the permission belongs to 279 * @param permission Name of the permission 280 * @param mask Mask of permissions to set 281 * @param flags Permissions to set 282 */ setPermissionFlags(@onNull String packageName, @NonNull String permission, int mask, int flags)283 public static void setPermissionFlags(@NonNull String packageName, @NonNull String permission, 284 int mask, int flags) { 285 runWithShellPermissionIdentity( 286 () -> sContext.getPackageManager().updatePermissionFlags(permission, packageName, 287 mask, flags, UserHandle.getUserHandleForUid(Process.myUid())), 288 GRANT_RUNTIME_PERMISSIONS); 289 } 290 291 /** 292 * Get all permissions an app requests. This includes the split permissions. 293 * 294 * @param packageName The package that requests the permissions. 295 * 296 * @return The permissions requested by the app 297 */ getPermissions(@onNull String packageName)298 public static @NonNull List<String> getPermissions(@NonNull String packageName) 299 throws Exception { 300 PackageInfo appInfo = sContext.getPackageManager().getPackageInfo(packageName, 301 GET_PERMISSIONS); 302 303 return Arrays.asList(appInfo.requestedPermissions); 304 } 305 306 /** 307 * Get all runtime permissions that an app requests. This includes the split permissions. 308 * 309 * @param packageName The package that requests the permissions. 310 * 311 * @return The runtime permissions requested by the app 312 */ getRuntimePermissions(@onNull String packageName)313 public static @NonNull List<String> getRuntimePermissions(@NonNull String packageName) 314 throws Exception { 315 ArrayList<String> runtimePermissions = new ArrayList<>(); 316 317 for (String perm : getPermissions(packageName)) { 318 PermissionInfo info = sContext.getPackageManager().getPermissionInfo(perm, 0); 319 if ((info.getProtection() & PROTECTION_DANGEROUS) != 0) { 320 runtimePermissions.add(perm); 321 } 322 } 323 324 return runtimePermissions; 325 } 326 327 } 328