summaryrefslogtreecommitdiff
path: root/common/device-side/util-axt/src/com/android/compatibility/common/util/PermissionUtils.java
diff options
context:
space:
mode:
Diffstat (limited to 'common/device-side/util-axt/src/com/android/compatibility/common/util/PermissionUtils.java')
-rw-r--r--common/device-side/util-axt/src/com/android/compatibility/common/util/PermissionUtils.java309
1 files changed, 309 insertions, 0 deletions
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/PermissionUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/PermissionUtils.java
new file mode 100644
index 00000000000..26f60560ce8
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/PermissionUtils.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.permissionToOp;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import android.Manifest;
+import android.annotation.TargetApi;
+import android.app.AppOpsManager;
+import android.app.UiAutomation;
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Assert;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Utility methods for permission-related functionality.
+ */
+public final class PermissionUtils {
+ private static final UiAutomation sUiAutomation =
+ InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ private static final String PLATFORM_PACKAGE_NAME = "android";
+
+ private static final String LOG_TAG = "PermissionUtils";
+
+ /**
+ * Checks if a permission is granted for a package.
+ *
+ * <p>This correctly handles pre-M apps by checking the app-ops instead.</p>
+ *
+ * @param packageName The package that might have the permission granted
+ * @param permission The permission that might be granted
+ * @return {@code true} iff the permission and app op are granted
+ */
+ public static boolean isPermissionAndAppOpGranted(@NonNull String packageName,
+ @NonNull String permission) throws Exception {
+ if (!isPermissionGranted(packageName, permission)) {
+ return false;
+ }
+ return getAppOp(packageName, permission) != MODE_IGNORED;
+ }
+
+ /**
+ * Grant a permission to an app.
+ *
+ * <p>This correctly handles pre-M apps by setting the app-ops.</p>
+ *
+ * @param packageName The app that should have the permission granted
+ * @param permission The permission to grant
+ */
+ @TargetApi(28)
+ public static void grantPermissionAndAppOp(@NonNull String packageName,
+ @NonNull String permission) {
+ sUiAutomation.grantRuntimePermission(packageName, permission);
+
+ setAppOpByPermission(packageName, permission, MODE_ALLOWED);
+ }
+
+ /**
+ * Revoke a permission from an app.
+ *
+ * <p>This correctly handles pre-M apps by setting the app-ops.</p>
+ *
+ * @param packageName The app that should have the permission revoked
+ * @param permission The permission to revoke
+ */
+ @TargetApi(28)
+ public static void revokePermissionAndAppOp(@NonNull String packageName,
+ @NonNull String permission) {
+ sUiAutomation.revokeRuntimePermission(packageName, permission);
+
+ setAppOpByPermission(packageName, permission, MODE_IGNORED);
+ }
+
+ /**
+ * Get all permissions an app requests. This includes the split permissions.
+ *
+ * @param packageName The package that requests the permissions.
+ * @return The permissions requested by the app
+ */
+ @NonNull
+ public static List<String> getPermissions(@NonNull String packageName)
+ throws Exception {
+ PackageInfo appInfo = getTargetContext().getPackageManager().getPackageInfo(packageName,
+ GET_PERMISSIONS);
+
+ return appInfo.requestedPermissions == null
+ ? Collections.emptyList()
+ : Arrays.asList(appInfo.requestedPermissions);
+ }
+
+ // Enforce non instantiability with a private constructor
+ private PermissionUtils() {
+ }
+
+ /**
+ * Get the state of an app-op.
+ *
+ * @param packageName The package for which the app-op is retrieved
+ * @param permission The permission to which the app-op is associated
+ * @return the app-op mode
+ */
+ @TargetApi(29)
+ private static int getAppOp(@NonNull String packageName, @NonNull String permission)
+ throws Exception {
+ return callWithShellPermissionIdentity(
+ () -> getTargetContext().getSystemService(AppOpsManager.class).unsafeCheckOpRaw(
+ permissionToOp(permission),
+ getTargetContext().getPackageManager().getPackageUid(
+ packageName, 0), packageName));
+ }
+
+ /**
+ * Set a new state for an app-op
+ *
+ * <p>Uses the permission-name for new state.</p>
+ *
+ * @param packageName The package for which the app-op is retrieved
+ * @param permission The permission to which the app-op is associated
+ * @param mode The new mode
+ */
+ @TargetApi(24)
+ private static void setAppOpByPermission(@NonNull String packageName,
+ @NonNull String permission, int mode) {
+ setAppOpByName(packageName, permissionToOp(permission), mode);
+ }
+
+ /**
+ * Set a new state for an app-op (using the app-op-name)
+ *
+ * @param packageName The package for which the app-op is retrieved
+ * @param op The name of the op
+ * @param mode The new mode
+ */
+ @TargetApi(24)
+ private static void setAppOpByName(@NonNull String packageName, @NonNull String op, int mode) {
+ runWithShellPermissionIdentity(
+ () -> getTargetContext().getSystemService(AppOpsManager.class).setUidMode(op,
+ getTargetContext().getPackageManager()
+ .getPackageUid(packageName, 0), mode));
+ }
+
+ /**
+ * Checks a permission. Does <u>not</u> check the appOp.
+ *
+ * <p>Users should use {@link #isPermissionAndAppOpGranted} instead.</p>
+ *
+ * @param packageName The package that might have the permission granted
+ * @param permission The permission that might be granted
+ * @return {@code true} iff the permission is granted
+ */
+ private static boolean isPermissionGranted(@NonNull String packageName,
+ @NonNull String permission) {
+ return getTargetContext().getPackageManager().checkPermission(permission, packageName)
+ == PERMISSION_GRANTED;
+ }
+
+ /**
+ * Returns if the device is a handheld device.
+ */
+ @TargetApi(23)
+ public static boolean isHandheld(Context context) {
+ final PackageManager pm = context.getPackageManager();
+ return pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
+ && !pm.hasSystemFeature(PackageManager.FEATURE_WATCH)
+ && !pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION)
+ && !pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+ }
+
+ /**
+ * Get package names of the applications hlding the role.
+ *
+ * @param roleName the name of the role to get the role holder for
+ * @return a list of package names of the role holders, or an empty list if none.
+ */
+ @TargetApi(29)
+ @NonNull
+ public static List<String> getRoleHolders(String roleName) throws Exception {
+ return callWithShellPermissionIdentity(() -> getTargetContext()
+ .getSystemService(RoleManager.class).getRoleHolders(roleName),
+ Manifest.permission.MANAGE_ROLE_HOLDERS);
+ }
+
+ /**
+ * Returns the app holding the specified role, or null if no app has the role. If more than one
+ * app holds the role, throws an {@link IllegalStateException}.
+ *
+ * @param roleName The target role name.
+ * @return The package name of the app holding the role, or null if not app has the role.
+ */
+ @Nullable
+ public static String getRoleHolder(String roleName) throws Exception {
+ List<String> packageNames = getRoleHolders(roleName);
+ if (packageNames.isEmpty()) {
+ return null;
+ } else if (packageNames.size() == 1) {
+ return packageNames.get(0);
+ } else {
+ throw new IllegalStateException("Expected only 1 package to hold the role \""
+ + roleName + "\" but found " + packageNames.size() + ": " + packageNames);
+ }
+ }
+
+ /**
+ * Check if it is a privileged permission.
+ *
+ * @return {@code true} iff it is a privileged permission
+ */
+ @TargetApi(28)
+ public static boolean isPrivilegedPerm(PermissionInfo permissionInfo) {
+ return permissionInfo != null
+ && ((permissionInfo.getProtection() & PermissionInfo.PROTECTION_SIGNATURE) != 0)
+ && ((permissionInfo.getProtectionFlags()
+ & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0);
+ }
+
+ /**
+ * Check if a package has any privileged permission granted.
+ *
+ * @param packageName The name of the package.
+ * @return {@code true} if the package has any privileged permission granted.
+ */
+ public static boolean isPkgHasPrivilegedPermGranted(@NonNull String packageName)
+ throws Exception {
+ final PackageManager pm = getTargetContext().getPackageManager();
+ PackageInfo pkgInfo = pm.getPackageInfo(packageName, GET_PERMISSIONS);
+ if (pkgInfo.requestedPermissions == null || pkgInfo.requestedPermissionsFlags == null) {
+ return false;
+ }
+ Assert.assertEquals(
+ String.format(Locale.US,
+ "[PackageInfo Error]: package(%s) requestedPermissions.length(%d) does "
+ + "not equal to "
+ + "requestedPermissionsFlags.length(%d)",
+ pkgInfo.packageName,
+ pkgInfo.requestedPermissions.length,
+ pkgInfo.requestedPermissionsFlags.length),
+ pkgInfo.requestedPermissions.length,
+ pkgInfo.requestedPermissionsFlags.length);
+ for (int i = 0; i < pkgInfo.requestedPermissions.length; i++) {
+ if ((pkgInfo.requestedPermissionsFlags[i] & PackageInfo.REQUESTED_PERMISSION_GRANTED)
+ != 0) {
+ try {
+ PermissionInfo permissionInfo = pm.getPermissionInfo(
+ pkgInfo.requestedPermissions[i], 0);
+ if (permissionInfo != null) {
+ return isPrivilegedPerm(permissionInfo);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.i(LOG_TAG, String.format("Permission %s not found",
+ pkgInfo.requestedPermissions[i]), e);
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check if a package is a platform signed app.
+ * Compare the given app cert with the cert of "android" package on the device.
+ * If both are same, then app is platform signed.
+ *
+ * @param packageName The name of the package.
+ * @return {@code true} if the package is a platform signed app.
+ */
+ public static boolean isPlatformSigned(@NonNull String packageName) {
+ final PackageManager pm = getTargetContext().getPackageManager();
+ return pm.checkSignatures(packageName, PLATFORM_PACKAGE_NAME)
+ == PackageManager.SIGNATURE_MATCH;
+ }
+
+ private static Context getTargetContext() {
+ return InstrumentationRegistry.getInstrumentation().getTargetContext();
+ }
+}