Add permission granting/revoking logic.
This changes adds the default permission granting/revoking logic
migrated from DefaultPolicyGrantPolicy, with added tri-state
permission, pre-M app and app op support.
Bug: 110557011
Test: build
Change-Id: I3b7bba98f917adf0e44cdcb8b12060670b6b3bcf
diff --git a/PermissionController/res/xml/roles.xml b/PermissionController/res/xml/roles.xml
index 14d6b22..3df3da1 100644
--- a/PermissionController/res/xml/roles.xml
+++ b/PermissionController/res/xml/roles.xml
@@ -184,12 +184,7 @@
<permission-set name="camera" />
</permissions>
<app-ops>
- <app-op name="android:read_sms" mode="allowed" />
<app-op name="android:write_sms" mode="allowed" />
- <app-op name="android:receive_sms" mode="allowed" />
- <app-op name="android:receive_wap_push" mode="allowed" />
- <app-op name="android:send_sms" mode="allowed" />
- <app-op name="android:read_cell_broadcasts" mode="allowed" />
</app-ops>
<preferred-activities>
<preferred-activiy>
diff --git a/PermissionController/src/com/android/packageinstaller/permission/utils/ArrayUtils.java b/PermissionController/src/com/android/packageinstaller/permission/utils/ArrayUtils.java
index 2af641b..ec59bcc 100644
--- a/PermissionController/src/com/android/packageinstaller/permission/utils/ArrayUtils.java
+++ b/PermissionController/src/com/android/packageinstaller/permission/utils/ArrayUtils.java
@@ -18,12 +18,25 @@
import android.text.TextUtils;
+import androidx.annotation.Nullable;
+
import java.util.Objects;
public final class ArrayUtils {
private ArrayUtils() { /* cannot be instantiated */ }
/**
+ * Checks if an array is null or has no elements.
+ *
+ * @param array the array to check for
+ *
+ * @return whether the array is null or has no elements.
+ */
+ public static <T> boolean isEmpty(@Nullable T[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
* Checks that value is present as at least one of the elements of the array.
* @param array the array to check in
* @param value the value to check for
diff --git a/PermissionController/src/com/android/packageinstaller/permission/utils/CollectionUtils.java b/PermissionController/src/com/android/packageinstaller/permission/utils/CollectionUtils.java
new file mode 100644
index 0000000..c4a64d6
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/permission/utils/CollectionUtils.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 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.packageinstaller.permission.utils;
+
+import android.util.ArraySet;
+
+/**
+ * Utility methods for dealing with {@link java.util.Collection}s.
+ */
+public final class CollectionUtils {
+
+ private CollectionUtils() {}
+
+ /**
+ * Remove all values in the array set that do <b>not</b> exist in the given collection.
+ *
+ * @param <T> the class of the elements to retain and of the {@code ArraySet}
+ * @param arraySet the {@code ArraySet} whose elements are to be removed or retained
+ * @param valuesToRetain the values to be used to determine which elements to retain
+ *
+ * @return {@code true} if any values were removed from the array set, {@code false} otherwise.
+ *
+ * @see ArraySet#retainAll(java.util.Collection)
+ */
+ @SafeVarargs
+ public static <T> boolean retainAll(ArraySet<T> arraySet, T... valuesToRetain) {
+ boolean removed = false;
+ for (int i = arraySet.size() - 1; i >= 0; i--) {
+ if (!ArrayUtils.contains(valuesToRetain, arraySet.valueAt(i))) {
+ arraySet.removeAt(i);
+ removed = true;
+ }
+ }
+ return removed;
+ }
+}
diff --git a/PermissionController/src/com/android/packageinstaller/role/model/AppOp.java b/PermissionController/src/com/android/packageinstaller/role/model/AppOp.java
index 6756d6d..7ed95c7 100644
--- a/PermissionController/src/com/android/packageinstaller/role/model/AppOp.java
+++ b/PermissionController/src/com/android/packageinstaller/role/model/AppOp.java
@@ -18,12 +18,8 @@
import android.app.AppOpsManager;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PermissionInfo;
-import android.os.Build;
import android.os.UserHandle;
-import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -77,7 +73,7 @@
if (grantedMode == null) {
return;
}
- setAppOpMode(packageName, mName, grantedMode, context);
+ Permissions.setAppOpMode(packageName, mName, grantedMode, context);
}
/**
@@ -96,33 +92,34 @@
@Nullable
private Integer computeGrantedMode(@NonNull String packageName, boolean overrideUser,
@NonNull Context context) {
- String permission = AppOpsManager.opToPermission(mName);
+ String permission = Permissions.getPermissionAppOp(mName);
if (permission == null) {
// No permission associated with this app op, just return our mode.
return mMode;
}
- boolean permissionFixed = isPermissionFixed(packageName, permission, overrideUser,
- context);
- String backgroundPermission = getBackgroundPermission(permission, context);
+ boolean isPermissionFixed = Permissions.isPermissionFixed(packageName, permission, false,
+ overrideUser, context);
+ String backgroundPermission = Permissions.getBackgroundPermission(permission, context);
if (isAppOpModePermissive(mMode)) {
- if (!isPermissionGranted(packageName, permission, context)) {
+ if (!Permissions.isPermissionGrantedWithoutCheckingAppOp(packageName, permission,
+ context)) {
// The permission isn't granted, we can't set the mode to permissive.
return null;
}
- Integer currentMode = getAppOpMode(packageName, mName, context);
- boolean currentModePermissive = currentMode != null && isAppOpModePermissive(
+ Integer currentMode = Permissions.getAppOpMode(packageName, mName, context);
+ boolean isCurrentModePermissive = currentMode != null && isAppOpModePermissive(
currentMode);
- if (permissionFixed && !currentModePermissive) {
+ if (isPermissionFixed && !isCurrentModePermissive) {
// The permission is fixed to a non-permissive mode, we can't set it to a permissive
// mode.
return null;
}
if (backgroundPermission == null) {
- if (permissionFixed) {
+ if (isPermissionFixed) {
// The permission doesn't have a background permission and is fixed, we can't
// change anything.
return null;
@@ -133,17 +130,19 @@
}
}
- if (isRuntimePermissionsSupported(packageName, context)) {
+ if (Permissions.isRuntimePermissionsSupported(packageName, context)) {
// Foreground permission is granted, derive the mode from whether the background
// permission is granted.
- if (isPermissionGranted(packageName, backgroundPermission, context)) {
+ if (Permissions.isPermissionGrantedWithoutCheckingAppOp(packageName,
+ backgroundPermission, context)) {
return AppOpsManager.MODE_ALLOWED;
} else {
return AppOpsManager.MODE_FOREGROUND;
}
} else {
- if (isPermissionFixed(packageName, backgroundPermission, overrideUser, context)) {
- if (currentModePermissive) {
+ if (Permissions.isPermissionFixed(packageName, backgroundPermission, false,
+ overrideUser, context)) {
+ if (isCurrentModePermissive) {
// The background permission is fixed to a permissive mode, we can't change
// anything.
return null;
@@ -161,7 +160,7 @@
return mMode;
}
} else {
- if (permissionFixed) {
+ if (isPermissionFixed) {
// The permission is fixed, we can't set the mode to another non-permissive mode.
return null;
}
@@ -172,7 +171,8 @@
return mMode;
}
- if (isPermissionFixed(packageName, backgroundPermission, overrideUser, context)) {
+ if (Permissions.isPermissionFixed(packageName, backgroundPermission, false,
+ overrideUser, context)) {
// The background permission is fixed, we can't change anything.
return null;
}
@@ -196,7 +196,7 @@
if (revokedMode == null) {
return;
}
- setAppOpMode(packageName, mName, revokedMode, context);
+ Permissions.setAppOpMode(packageName, mName, revokedMode, context);
}
/**
@@ -216,22 +216,24 @@
String permission = AppOpsManager.opToPermission(mName);
if (permission == null) {
// No permission associated with this app op, just return the default mode.
- return getDefaultAppOpMode(mName);
+ return Permissions.getDefaultAppOpMode(mName);
}
- String backgroundPermission = getBackgroundPermission(permission, context);
- boolean permissionFixed = isPermissionFixed(packageName, permission, false, context);
- boolean runtimePermissionsSupported = isRuntimePermissionsSupported(packageName, context);
+ String backgroundPermission = Permissions.getBackgroundPermission(permission, context);
+ boolean isPermissionFixed = Permissions.isPermissionFixed(packageName, permission, false,
+ false, context);
+ boolean isRuntimePermissionsSupported = Permissions.isRuntimePermissionsSupported(
+ packageName, context);
if (backgroundPermission == null) {
- if (permissionFixed) {
+ if (isPermissionFixed) {
// The permission doesn't have a background permission and is fixed, we can't change
// anything.
return null;
} else {
// The permission doesn't have a background permission and isn't fixed, return the
// default mode.
- int defaultMode = getDefaultAppOpMode(mName);
- if (!runtimePermissionsSupported) {
+ int defaultMode = Permissions.getDefaultAppOpMode(mName);
+ if (!isRuntimePermissionsSupported) {
// The app doesn't support runtime permissions, let the user decide whether it
// gets the permission if we reset it to a permissive mode.
addPermissionReviewRequiredFlagForPermissiveAppOpMode(packageName, permission,
@@ -241,27 +243,29 @@
}
}
- if (runtimePermissionsSupported) {
- if (isPermissionGranted(packageName, permission, context)) {
+ if (isRuntimePermissionsSupported) {
+ if (Permissions.isPermissionGrantedWithoutCheckingAppOp(packageName,
+ permission, context)) {
// Foreground permission is granted, derive the mode from whether the background
// permission is granted.
- if (isPermissionGranted(packageName, backgroundPermission, context)) {
+ if (Permissions.isPermissionGrantedWithoutCheckingAppOp(packageName,
+ backgroundPermission, context)) {
return AppOpsManager.MODE_ALLOWED;
} else {
return AppOpsManager.MODE_FOREGROUND;
}
} else {
- if (permissionFixed) {
+ if (isPermissionFixed) {
// Foreground permission is fixed to revoked, we can't change anything.
return null;
}
// Return the default mode.
- return getDefaultAppOpMode(mName);
+ return Permissions.getDefaultAppOpMode(mName);
}
} else {
- Integer currentMode = getAppOpMode(packageName, mName, context);
- boolean backgroundPermissionFixed = isPermissionFixed(packageName, backgroundPermission,
- false, context);
+ Integer currentMode = Permissions.getAppOpMode(packageName, mName, context);
+ boolean backgroundPermissionFixed = Permissions.isPermissionFixed(packageName,
+ backgroundPermission, false, false, context);
if (backgroundPermissionFixed) {
if (currentMode != null && currentMode == AppOpsManager.MODE_ALLOWED) {
// The background permission is fixed to MODE_ALLOWED, we can't change anything.
@@ -270,7 +274,7 @@
// The background permission is fixed with a mode other than MODE_ALLOWED, keep
// going.
}
- if (permissionFixed) {
+ if (isPermissionFixed) {
if (currentMode != null && currentMode == AppOpsManager.MODE_ALLOWED) {
// The foreground permission is fixed with MODE_ALLOWED, and since we got here
// the background permission is not fixed, so we set the mode to
@@ -282,7 +286,7 @@
return null;
}
// Return the default mode.
- int defaultMode = getDefaultAppOpMode(mName);
+ int defaultMode = Permissions.getDefaultAppOpMode(mName);
if (backgroundPermissionFixed) {
if (defaultMode == AppOpsManager.MODE_ALLOWED) {
// The background permission is fixed with a mode other than MODE_ALLOWED, so
@@ -296,89 +300,6 @@
}
}
- // TODO: Move into Permissions
- /**
- * Check if an application supports runtime permissions.
- *
- * @param packageName the package name of the application
- * @param context the {@code Context} to retrieve system services
- *
- * @return whether the application supports runtime permissions, or {@code false} if the check
- * failed.
- */
- private static boolean isRuntimePermissionsSupported(@NonNull String packageName,
- @NonNull Context context) {
- ApplicationInfo applicationInfo = getApplicationInfo(packageName, context);
- if (applicationInfo == null) {
- return false;
- }
- return applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M;
- }
-
- // TODO: Move into Permissions
- /**
- * Check if a permission is fixed by flags.
- *
- * @param packageName the package name of the application
- * @param permission the name of the permission
- * @param overrideUser whether user's permission settings can be overridden
- * @param context the {@code Context} to retrieve system services
- *
- * @return whether the permission is fixed by flags.
- */
- private boolean isPermissionFixed(@NonNull String packageName, @NonNull String permission,
- boolean overrideUser, @NonNull Context context) {
- PackageManager packageManager = context.getPackageManager();
- UserHandle user = UserHandle.of(UserHandle.myUserId());
- int flags = packageManager.getPermissionFlags(permission, packageName, user);
- int fixedFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
- | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
- if (!overrideUser) {
- fixedFlags |= PackageManager.FLAG_PERMISSION_USER_FIXED
- | PackageManager.FLAG_PERMISSION_USER_SET;
- }
- return (flags & fixedFlags) != 0;
- }
-
- // TODO: Move into Permissions
- /**
- * Check if a permission is granted to an application.
- *
- * @param packageName the package name of the application
- * @param permission the name of the permission
- * @param context the {@code Context} to retrieve system services
- *
- * @return whether the permission is granted to the application.
- */
- private static boolean isPermissionGranted(@NonNull String packageName,
- @NonNull String permission, @NonNull Context context) {
- return context.getPackageManager().checkPermission(permission, packageName)
- == PackageManager.PERMISSION_GRANTED;
- }
-
- // TODO: Move into Permissions
- /**
- * Retrieve the background permission of a permission.
- *
- * @param permission the name of the permission
- * @param context the {@code Context} to retrieve system services
- *
- * @return the background permission of the permission, or {@code null} if none
- */
- @Nullable
- private static String getBackgroundPermission(@NonNull String permission,
- @NonNull Context context) {
- PermissionInfo permissionInfo;
- try {
- permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0);
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(LOG_TAG, "Cannot get PermissionInfo for permission: " + permission, e);
- return null;
- }
- return permissionInfo.backgroundPermission;
- }
-
- // TODO: Move into Permissions
/**
* Add {@link PackageManager#FLAG_PERMISSION_REVIEW_REQUIRED} for the permission if the app op
* mode is permissive.
@@ -413,85 +334,6 @@
return mode == AppOpsManager.MODE_ALLOWED || mode == AppOpsManager.MODE_FOREGROUND;
}
- /**
- * Retrieve an app op mode for an application.
- *
- * @param packageName the package name of the application
- * @param appOp the name of the app op to retrieve
- * @param context the {@code Context} to retrieve system services
- *
- * @return the app op mode for the application, or {@code null} if it cannot be retrieved
- */
- @Nullable
- private static Integer getAppOpMode(@NonNull String packageName,
- @NonNull String appOp, @NonNull Context context) {
- ApplicationInfo applicationInfo = getApplicationInfo(packageName, context);
- if (applicationInfo == null) {
- return null;
- }
- AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
- return appOpsManager.unsafeCheckOpRaw(appOp, applicationInfo.uid, packageName);
- }
-
- /**
- * Retrieve the default mode of an app op
- *
- * @param appOp the name of the app op to retrieve
- *
- * @return the default mode of the app op
- */
- private static int getDefaultAppOpMode(@NonNull String appOp) {
- return AppOpsManager.opToDefaultMode(appOp);
- }
-
- /**
- * Set an app op mode for an application.
- *
- * @param packageName the package name of the application
- * @param appOp the name of the app op to set
- * @param mode the mode of the app op to set
- * @param context the {@code Context} to retrieve system services
- *
- * @return whether app op mode is changed
- */
- private static boolean setAppOpMode(@NonNull String packageName, @NonNull String appOp,
- int mode, @NonNull Context context) {
- Integer currentMode = getAppOpMode(packageName, appOp, context);
- if (currentMode != null && currentMode == mode) {
- return false;
- }
- ApplicationInfo applicationInfo = getApplicationInfo(packageName, context);
- if (applicationInfo == null) {
- Log.e(LOG_TAG, "Cannot get ApplicationInfo for package to set app op mode: "
- + packageName);
- return false;
- }
- AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
- appOpsManager.setUidMode(appOp, applicationInfo.uid, mode);
- return true;
- }
-
- /**
- * Retrieve the {@link ApplicationInfo} of an application.
- *
- * @param packageName the package name of the application
- * @param context the {@code Context} to retrieve system services
- *
- * @return the {@link ApplicationInfo} of the application, or {@code null} if it cannot be
- * retrieved
- */
- @Nullable
- private static ApplicationInfo getApplicationInfo(@NonNull String packageName,
- @NonNull Context context) {
- try {
- return context.getPackageManager().getApplicationInfo(packageName,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
- } catch (PackageManager.NameNotFoundException e) {
- return null;
- }
- }
-
@Override
public String toString() {
return "AppOp{"
diff --git a/PermissionController/src/com/android/packageinstaller/role/model/Permissions.java b/PermissionController/src/com/android/packageinstaller/role/model/Permissions.java
new file mode 100644
index 0000000..5a28780
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/role/model/Permissions.java
@@ -0,0 +1,732 @@
+/*
+ * Copyright (C) 2018 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.packageinstaller.role.model;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.os.Build;
+import android.os.UserHandle;
+import android.permission.PermissionManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.packageinstaller.permission.utils.ArrayUtils;
+import com.android.packageinstaller.permission.utils.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Permissions to be granted or revoke by a {@link Role}.
+ */
+public class Permissions {
+
+ private static final String LOG_TAG = Permissions.class.getSimpleName();
+
+ private static final boolean DEBUG = false;
+
+ private static ArrayMap<String, String> sForegroundToBackgroundPermission;
+ private static ArrayMap<String, List<String>> sBackgroundToForegroundPermissions;
+ private static final Object sForegroundBackgroundPermissionMappingsLock = new Object();
+
+ /**
+ * Grant permissions and associated app ops to an application.
+ *
+ * @param packageName the package name of the application to be granted permissions to
+ * @param permissions the list of permissions to be granted
+ * @param overrideDisabledSystemPackageAndUser whether to ignore the permissions of a disabled
+ * system package (if this package is an updated
+ * system package), and whether to override user
+ * set and fixed flags on the permission.
+ * @param setSystemFixed whether the permissions will be granted as system-fixed
+ * @param context the {@code Context} to retrieve system services.
+ *
+ * @return whether any app mode has changed
+ *
+ * @see com.android.server.pm.permission.DefaultPermissionGrantPolicy#grantRuntimePermissions(
+ * PackageInfo, java.util.Set, boolean, boolean, int)
+ */
+ public static boolean grant(@NonNull String packageName, @NonNull List<String> permissions,
+ boolean overrideDisabledSystemPackageAndUser, boolean setSystemFixed,
+ @NonNull Context context) {
+ PackageInfo packageInfo = getPackageInfo(packageName, context);
+ if (packageInfo == null) {
+ return false;
+ }
+
+ if (ArrayUtils.isEmpty(packageInfo.requestedPermissions)) {
+ return false;
+ }
+
+ // Automatically attempt to grant split permissions to older APKs
+ PermissionManager permissionManager = context.getSystemService(PermissionManager.class);
+ List<PermissionManager.SplitPermissionInfo> splitPermissions =
+ permissionManager.getSplitPermissions();
+ ArraySet<String> permissionsWithoutSplits = new ArraySet<>(permissions);
+ ArraySet<String> permissionsToGrant = new ArraySet<>(permissionsWithoutSplits);
+ int splitPermissionsSize = splitPermissions.size();
+ for (int i = 0; i < splitPermissionsSize; i++) {
+ PermissionManager.SplitPermissionInfo splitPermission = splitPermissions.get(i);
+
+ if (packageInfo.applicationInfo.targetSdkVersion < splitPermission.getTargetSdk()
+ && permissionsWithoutSplits.contains(splitPermission.getSplitPermission())) {
+ permissionsToGrant.addAll(splitPermission.getNewPermissions());
+ }
+ }
+
+ CollectionUtils.retainAll(permissionsToGrant, packageInfo.requestedPermissions);
+ if (permissionsToGrant.isEmpty()) {
+ return false;
+ }
+
+ // In some cases, like for the Phone or SMS app, we grant permissions regardless
+ // of if the version on the system image declares the permission as used since
+ // selecting the app as the default for that function the user makes a deliberate
+ // choice to grant this app the permissions needed to function. For all other
+ // apps, (default grants on first boot and user creation) we don't grant default
+ // permissions if the version on the system image does not declare them.
+ if (!overrideDisabledSystemPackageAndUser && isUpdatedSystemApp(packageInfo)) {
+ PackageInfo disabledSystemPackageInfo = getFactoryPackageInfo(packageName, context);
+ if (disabledSystemPackageInfo != null) {
+ if (ArrayUtils.isEmpty(disabledSystemPackageInfo.requestedPermissions)) {
+ return false;
+ }
+ CollectionUtils.retainAll(permissionsToGrant,
+ disabledSystemPackageInfo.requestedPermissions);
+ if (permissionsToGrant.isEmpty()) {
+ return false;
+ }
+ }
+ }
+
+ // Sort foreground permissions first so that we can grant a background permission based on
+ // whether any of its foreground permissions are granted.
+ int permissionsToGrantSize = permissionsToGrant.size();
+ String[] sortedPermissionsToGrant = new String[permissionsToGrantSize];
+ int foregroundPermissionCount = 0;
+ int nonForegroundPermissionCount = 0;
+ for (int i = 0; i < permissionsToGrantSize; i++) {
+ String permission = permissionsToGrant.valueAt(i);
+
+ if (isForegroundPermission(permission, context)) {
+ sortedPermissionsToGrant[foregroundPermissionCount] = permission;
+ foregroundPermissionCount++;
+ } else {
+ int index = permissionsToGrantSize - 1 - nonForegroundPermissionCount;
+ sortedPermissionsToGrant[index] = permission;
+ nonForegroundPermissionCount++;
+ }
+ }
+
+ boolean appOpModeChanged = false;
+ int sortedPermissionsToGrantLength = sortedPermissionsToGrant.length;
+ for (int i = 0; i < sortedPermissionsToGrantLength; i++) {
+ String permission = sortedPermissionsToGrant[i];
+
+ appOpModeChanged |= grantSingle(packageName, permission,
+ overrideDisabledSystemPackageAndUser, setSystemFixed, context);
+ }
+
+ return appOpModeChanged;
+ }
+
+ private static boolean grantSingle(@NonNull String packageName, @NonNull String permission,
+ boolean overrideUserSetAndFixed, boolean setSystemFixed, @NonNull Context context) {
+ boolean wasPermissionOrAppOpGranted = isPermissionOrAppOpGranted(packageName, permission,
+ context);
+ if (isPermissionFixed(packageName, permission, false, overrideUserSetAndFixed, context)
+ && !wasPermissionOrAppOpGranted) {
+ // Stop granting if this permission is fixed to revoked.
+ return false;
+ }
+
+ if (isBackgroundPermission(permission, context)) {
+ List<String> foregroundPermissions = getForegroundPermissions(permission, context);
+ boolean isAnyForegroundPermissionGranted = false;
+ int foregroundPermissionsSize = foregroundPermissions.size();
+ for (int i = 0; i < foregroundPermissionsSize; i++) {
+ String foregroundPermission = foregroundPermissions.get(i);
+
+ if (isPermissionOrAppOpGranted(packageName, foregroundPermission, context)) {
+ isAnyForegroundPermissionGranted = true;
+ break;
+ }
+ }
+
+ if (!isAnyForegroundPermissionGranted) {
+ // Stop granting if this background permission doesn't have a granted foreground
+ // permission.
+ return false;
+ }
+ }
+
+ boolean appOpModeChanged = grantPermissionAndAppOp(packageName, permission, context);
+
+ // Update permission flags.
+ int newFlags = PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+ if (setSystemFixed) {
+ newFlags |= PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+ }
+ int newMask = newFlags;
+ if (!wasPermissionOrAppOpGranted) {
+ // If we've granted a permission which wasn't granted, it's no longer user set or fixed.
+ newMask |= PackageManager.FLAG_PERMISSION_USER_FIXED
+ | PackageManager.FLAG_PERMISSION_USER_SET;
+ }
+ // TODO: Why this?
+ // If a component gets a permission for being the default handler A
+ // and also default handler B, we grant the weaker grant form.
+ if (!setSystemFixed) {
+ int oldFlags = getPermissionFlags(permission, packageName, context);
+ if ((oldFlags & PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0
+ && (oldFlags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Granted not fixed " + permission + " to default handler "
+ + packageName);
+ }
+ newMask |= PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+ }
+ }
+ setPermissionFlags(permission, packageName, newFlags, newMask, context);
+
+ return appOpModeChanged;
+ }
+
+ private static boolean isPermissionOrAppOpGranted(@NonNull String packageName,
+ @NonNull String permission, @NonNull Context context) {
+ if (!isPermissionGrantedWithoutCheckingAppOp(packageName, permission, context)) {
+ return false;
+ }
+
+ if (isRuntimePermissionsSupported(packageName, context)) {
+ return true;
+ }
+
+ // Check app op mode for pre-M apps.
+ String appOp = getPermissionAppOp(permission);
+ if (appOp == null) {
+ return false;
+ }
+ Integer appOpMode = getAppOpMode(packageName, appOp, context);
+ if (appOpMode == null) {
+ return false;
+ }
+
+ if (!isBackgroundPermission(permission, context)) {
+ if (!isForegroundPermission(permission, context)) {
+ // This permission is an ordinary permission, return true if its app op mode is
+ // MODE_ALLOWED.
+ return appOpMode == AppOpsManager.MODE_ALLOWED;
+ } else {
+ // This permission is a foreground permission, return true if its app op mode is
+ // MODE_FOREGROUND or MODE_ALLOWED.
+ return appOpMode == AppOpsManager.MODE_FOREGROUND
+ || appOpMode == AppOpsManager.MODE_ALLOWED;
+ }
+ } else {
+ // This permission is a background permission, return true if any of its foreground
+ // permissions' app op modes are MODE_ALLOWED.
+ List<String> foregroundPermissions = getForegroundPermissions(permission, context);
+ int foregroundPermissionsSize = foregroundPermissions.size();
+ for (int i = 0; i < foregroundPermissionsSize; i++) {
+ String foregroundPermission = foregroundPermissions.get(i);
+
+ String foregroundAppOp = getPermissionAppOp(foregroundPermission);
+ if (foregroundAppOp == null) {
+ continue;
+ }
+ Integer foregroundAppOpMode = getAppOpMode(packageName, foregroundAppOp, context);
+ if (foregroundAppOpMode == null) {
+ continue;
+ }
+ if (foregroundAppOpMode == AppOpsManager.MODE_ALLOWED) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private static boolean grantPermissionAndAppOp(@NonNull String packageName,
+ @NonNull String permission, @NonNull Context context) {
+ // Grant the permission.
+ PackageManager packageManager = context.getPackageManager();
+ UserHandle user = UserHandle.of(UserHandle.myUserId());
+ packageManager.grantRuntimePermission(packageName, permission, user);
+
+ // Grant the app op.
+ if (!isBackgroundPermission(permission, context)) {
+ String appOp = getPermissionAppOp(permission);
+ if (appOp == null) {
+ return false;
+ }
+
+ int appOpMode;
+ if (!isForegroundPermission(permission, context)) {
+ // This permission is an ordinary permission, set its app op mode to MODE_ALLOWED.
+ appOpMode = AppOpsManager.MODE_ALLOWED;
+ } else {
+ // This permission is a foreground permission, set its app op mode according to
+ // whether its background permission is granted.
+ String backgroundPermission = getBackgroundPermission(permission, context);
+ if (!isPermissionOrAppOpGranted(packageName, backgroundPermission, context)) {
+ appOpMode = AppOpsManager.MODE_FOREGROUND;
+ } else {
+ appOpMode = AppOpsManager.MODE_ALLOWED;
+ }
+ }
+ return setAppOpMode(packageName, appOp, appOpMode, context);
+ } else {
+ // This permission is a background permission, set all its foreground permissions' app
+ // op modes to MODE_ALLOWED.
+ boolean appOpModeChanged = false;
+ List<String> foregroundPermissions = getForegroundPermissions(permission, context);
+ int foregroundPermissionsSize = foregroundPermissions.size();
+ for (int i = 0; i < foregroundPermissionsSize; i++) {
+ String foregroundPermission = foregroundPermissions.get(i);
+
+ String foregroundAppOp = getPermissionAppOp(foregroundPermission);
+ if (foregroundAppOp == null) {
+ continue;
+ }
+ appOpModeChanged |= setAppOpMode(packageName, foregroundAppOp,
+ AppOpsManager.MODE_ALLOWED, context);
+ }
+ return appOpModeChanged;
+ }
+ }
+
+ /**
+ * Revoke permissions and associated app ops from an application.
+ *
+ * @param packageName the package name of the application to be revoke permissions from
+ * @param permissions the list of permissions to be revoked
+ * @param overrideSystemFixed whether system-fixed permissions will be revoked
+ * @param context the {@code Context} to retrieve system services.
+ *
+ * @see com.android.server.pm.permission.DefaultPermissionGrantPolicy#revokeRuntimePermissions(
+ * String, java.util.Set, boolean, int)
+ */
+ public boolean revoke(@NonNull String packageName, @NonNull List<String> permissions,
+ boolean overrideSystemFixed, @NonNull Context context) {
+ PackageInfo packageInfo = getPackageInfo(packageName, context);
+ if (packageInfo == null) {
+ return false;
+ }
+
+ if (ArrayUtils.isEmpty(packageInfo.requestedPermissions)) {
+ return false;
+ }
+
+ ArraySet<String> permissionsToRevoke = new ArraySet<>(permissions);
+ CollectionUtils.retainAll(permissionsToRevoke, packageInfo.requestedPermissions);
+ if (permissionsToRevoke.isEmpty()) {
+ return false;
+ }
+
+ // Sort background permissions first so that we can revoke a foreground permission based on
+ // whether its background permission is revoked.
+ int permissionsToRevokeSize = permissionsToRevoke.size();
+ String[] sortedPermissionsToRevoke = new String[permissionsToRevokeSize];
+ int backgroundPermissionCount = 0;
+ int nonBackgroundPermissionCount = 0;
+ for (int i = 0; i < permissionsToRevokeSize; i++) {
+ String permission = permissionsToRevoke.valueAt(i);
+
+ if (isBackgroundPermission(permission, context)) {
+ sortedPermissionsToRevoke[backgroundPermissionCount] = permission;
+ backgroundPermissionCount++;
+ } else {
+ int index = permissionsToRevokeSize - 1 - nonBackgroundPermissionCount;
+ sortedPermissionsToRevoke[index] = permission;
+ nonBackgroundPermissionCount++;
+ }
+ }
+
+ boolean appOpModeChanged = false;
+ int sortedPermissionsToRevokeLength = sortedPermissionsToRevoke.length;
+ for (int i = 0; i < sortedPermissionsToRevokeLength; i++) {
+ String permission = sortedPermissionsToRevoke[i];
+
+ appOpModeChanged |= revokeSingle(packageName, permission, overrideSystemFixed, context);
+ }
+
+ return appOpModeChanged;
+ }
+
+ private static boolean revokeSingle(@NonNull String packageName, @NonNull String permission,
+ boolean overrideSystemFixed, @NonNull Context context) {
+ if (!isPermissionGrantedByDefault(packageName, permission, context)) {
+ return false;
+ }
+
+ // Remove the granted-by-default permission flag.
+ setPermissionFlags(permission, packageName, 0,
+ PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, context);
+ // TODO: Why?
+ // Note that we do not revoke FLAG_PERMISSION_SYSTEM_FIXED. That bit remains sticky once
+ // set.
+
+ if (isPermissionFixed(packageName, permission, overrideSystemFixed, false, context)
+ && isPermissionOrAppOpGranted(packageName, permission, context)) {
+ // Stop revoking if this permission is fixed to granted.
+ return false;
+ }
+
+ if (isForegroundPermission(permission, context)) {
+ String backgroundPermission = getBackgroundPermission(permission, context);
+ if (isPermissionOrAppOpGranted(packageName, backgroundPermission, context)) {
+ // Stop revoking if this foreground permission has a granted background permission.
+ return false;
+ }
+ }
+
+ return revokePermissionAndAppOp(packageName, permission, context);
+ }
+
+ private static boolean revokePermissionAndAppOp(@NonNull String packageName,
+ @NonNull String permission, @NonNull Context context) {
+ boolean runtimePermissionsSupported = isRuntimePermissionsSupported(packageName, context);
+ PackageManager packageManager = context.getPackageManager();
+ UserHandle user = UserHandle.of(UserHandle.myUserId());
+ if (runtimePermissionsSupported) {
+ // Revoke the permission.
+ packageManager.revokeRuntimePermission(packageName, permission, user);
+ }
+
+ // Revoke the app op.
+ if (!isBackgroundPermission(permission, context)) {
+ String appOp = getPermissionAppOp(permission);
+ if (appOp == null) {
+ return false;
+ }
+
+ // This permission is an ordinary or foreground permission, reset its app op mode to
+ // default.
+ int appOpMode = getDefaultAppOpMode(appOp);
+ boolean appOpModeChanged = setAppOpMode(packageName, appOp, appOpMode, context);
+ if (appOpModeChanged) {
+ if (!runtimePermissionsSupported && (appOpMode == AppOpsManager.MODE_FOREGROUND
+ || appOpMode == AppOpsManager.MODE_ALLOWED)) {
+ // We've reset this permission's app op mode to be permissive, so we'll need the
+ // user to review it again.
+ packageManager.updatePermissionFlags(permission, packageName,
+ PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED,
+ PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED, user);
+ }
+ }
+ return appOpModeChanged;
+ } else {
+ // This permission is a background permission, set all its granted foreground
+ // permissions' app op modes to MODE_FOREGROUND.
+ boolean appOpModeChanged = false;
+ List<String> foregroundPermissions = getForegroundPermissions(permission, context);
+ int foregroundPermissionsSize = foregroundPermissions.size();
+ for (int i = 0; i < foregroundPermissionsSize; i++) {
+ String foregroundPermission = foregroundPermissions.get(i);
+
+ if (!isPermissionOrAppOpGranted(packageName, foregroundPermission, context)) {
+ continue;
+ }
+
+ String foregroundAppOp = getPermissionAppOp(foregroundPermission);
+ if (foregroundAppOp == null) {
+ continue;
+ }
+ appOpModeChanged |= setAppOpMode(packageName, foregroundAppOp,
+ AppOpsManager.MODE_FOREGROUND, context);
+ }
+ return appOpModeChanged;
+ }
+ }
+
+ @Nullable
+ private static PackageInfo getPackageInfo(@NonNull String packageName,
+ @NonNull Context context) {
+ return getPackageInfo(packageName, 0, context);
+ }
+
+ @Nullable
+ private static PackageInfo getFactoryPackageInfo(@NonNull String packageName,
+ @NonNull Context context) {
+ return getPackageInfo(packageName, PackageManager.MATCH_FACTORY_ONLY, context);
+ }
+
+ @Nullable
+ private static PackageInfo getPackageInfo(@NonNull String packageName, int extraFlags,
+ @NonNull Context context) {
+ try {
+ return context.getPackageManager().getPackageInfo(packageName,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ // TODO: Why MATCH_UNINSTALLED_PACKAGES?
+ | PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.GET_PERMISSIONS | extraFlags);
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ private static boolean isUpdatedSystemApp(@NonNull PackageInfo packageInfo) {
+ return packageInfo.applicationInfo != null && (packageInfo.applicationInfo.flags
+ & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+ }
+
+ @Nullable
+ static ApplicationInfo getApplicationInfo(@NonNull String packageName,
+ @NonNull Context context) {
+ try {
+ return context.getPackageManager().getApplicationInfo(packageName,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ static boolean isRuntimePermissionsSupported(@NonNull String packageName,
+ @NonNull Context context) {
+ ApplicationInfo applicationInfo = getApplicationInfo(packageName, context);
+ if (applicationInfo == null) {
+ return false;
+ }
+ return applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M;
+ }
+
+ private static int getPermissionFlags(@NonNull String packageName, @NonNull String permission,
+ @NonNull Context context) {
+ PackageManager packageManager = context.getPackageManager();
+ UserHandle user = UserHandle.of(UserHandle.myUserId());
+ return packageManager.getPermissionFlags(permission, packageName, user);
+ }
+
+ static boolean isPermissionFixed(@NonNull String packageName, @NonNull String permission,
+ boolean overrideSystemFixed, boolean overrideUserSetAndFixed,
+ @NonNull Context context) {
+ int flags = getPermissionFlags(packageName, permission, context);
+ int fixedFlags = PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+ if (!overrideSystemFixed) {
+ fixedFlags |= PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+ }
+ if (!overrideUserSetAndFixed) {
+ fixedFlags |= PackageManager.FLAG_PERMISSION_USER_FIXED
+ | PackageManager.FLAG_PERMISSION_USER_SET;
+ }
+ return (flags & fixedFlags) != 0;
+ }
+
+ private static boolean isPermissionGrantedByDefault(@NonNull String packageName,
+ @NonNull String permission, @NonNull Context context) {
+ int flags = getPermissionFlags(packageName, permission, context);
+ return (flags & PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0;
+ }
+
+ private static void setPermissionFlags(@NonNull String packageName, @NonNull String permission,
+ int flags, int mask, @NonNull Context context) {
+ PackageManager packageManager = context.getPackageManager();
+ UserHandle user = UserHandle.of(UserHandle.myUserId());
+ packageManager.updatePermissionFlags(permission, packageName, mask, flags, user);
+ }
+
+ /**
+ * Most of the time {@link #isPermissionOrAppOpGranted(String, String, Context)} should be used
+ * instead.
+ */
+ static boolean isPermissionGrantedWithoutCheckingAppOp(@NonNull String packageName,
+ @NonNull String permission, @NonNull Context context) {
+ return context.getPackageManager().checkPermission(permission, packageName)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private static boolean isForegroundPermission(@NonNull String permission,
+ @NonNull Context context) {
+ ensureForegroundBackgroundPermissionMappings(context);
+ return sForegroundToBackgroundPermission.containsKey(permission);
+ }
+
+ @Nullable
+ static String getBackgroundPermission(@NonNull String foregroundPermission,
+ @NonNull Context context) {
+ ensureForegroundBackgroundPermissionMappings(context);
+ return sForegroundToBackgroundPermission.get(foregroundPermission);
+ }
+
+ private static boolean isBackgroundPermission(@NonNull String permission,
+ @NonNull Context context) {
+ ensureForegroundBackgroundPermissionMappings(context);
+ return sBackgroundToForegroundPermissions.containsKey(permission);
+ }
+
+ @Nullable
+ private static List<String> getForegroundPermissions(@NonNull String backgroundPermission,
+ @NonNull Context context) {
+ ensureForegroundBackgroundPermissionMappings(context);
+ return sBackgroundToForegroundPermissions.get(backgroundPermission);
+ }
+
+ private static void ensureForegroundBackgroundPermissionMappings(@NonNull Context context) {
+ synchronized (sForegroundBackgroundPermissionMappingsLock) {
+ if (sForegroundToBackgroundPermission == null
+ && sBackgroundToForegroundPermissions == null) {
+ createForegroundBackgroundPermissionMappings(context);
+ }
+ }
+ }
+
+ private static void createForegroundBackgroundPermissionMappings(@NonNull Context context) {
+ List<String> permissions = new ArrayList<>();
+ sBackgroundToForegroundPermissions = new ArrayMap<>();
+
+ PackageManager packageManager = context.getPackageManager();
+ List<PermissionGroupInfo> permissionGroupInfos = packageManager.getAllPermissionGroups(0);
+
+ int permissionGroupInfosSize = permissionGroupInfos.size();
+ for (int permissionGroupInfosIndex = 0;
+ permissionGroupInfosIndex < permissionGroupInfosSize; permissionGroupInfosIndex++) {
+ PermissionGroupInfo permissionGroupInfo = permissionGroupInfos.get(
+ permissionGroupInfosIndex);
+
+ List<PermissionInfo> permissionInfos;
+ try {
+ permissionInfos = packageManager.queryPermissionsByGroup(permissionGroupInfo.name,
+ 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(LOG_TAG, "Cannot get permissions for group: " + permissionGroupInfo.name);
+ continue;
+ }
+
+ int permissionInfosSize = permissionInfos.size();
+ for (int permissionInfosIndex = 0; permissionInfosIndex < permissionInfosSize;
+ permissionInfosIndex++) {
+ PermissionInfo permissionInfo = permissionInfos.get(permissionInfosIndex);
+
+ String permission = permissionInfo.name;
+ permissions.add(permission);
+
+ String backgroundPermission = permissionInfo.backgroundPermission;
+ if (backgroundPermission != null) {
+ List<String> foregroundPermissions = sBackgroundToForegroundPermissions.get(
+ backgroundPermission);
+ if (foregroundPermissions == null) {
+ foregroundPermissions = new ArrayList<>();
+ sBackgroundToForegroundPermissions.put(backgroundPermission,
+ foregroundPermissions);
+ }
+ foregroundPermissions.add(permission);
+ }
+ }
+ }
+
+ // Remove background permissions declared by foreground permissions but don't actually
+ // exist.
+ sBackgroundToForegroundPermissions.retainAll(permissions);
+
+ // Collect foreground permissions that have existent background permissions.
+ sForegroundToBackgroundPermission = new ArrayMap<>();
+
+ int backgroundToForegroundPermissionsSize = sBackgroundToForegroundPermissions.size();
+ for (int backgroundToForegroundPermissionsIndex = 0;
+ backgroundToForegroundPermissionsIndex < backgroundToForegroundPermissionsSize;
+ backgroundToForegroundPermissionsIndex++) {
+ String backgroundPerimssion = sBackgroundToForegroundPermissions.keyAt(
+ backgroundToForegroundPermissionsIndex);
+ List<String> foregroundPermissions = sBackgroundToForegroundPermissions.valueAt(
+ backgroundToForegroundPermissionsIndex);
+
+ int foregroundPermissionsSize = foregroundPermissions.size();
+ for (int foregroundPermissionsIndex = 0;
+ foregroundPermissionsIndex < foregroundPermissionsSize;
+ foregroundPermissionsIndex++) {
+ String foregroundPermission = foregroundPermissions.get(foregroundPermissionsIndex);
+
+ sForegroundToBackgroundPermission.put(foregroundPermission, backgroundPerimssion);
+ }
+ }
+ }
+
+ @Nullable
+ static String getPermissionAppOp(@NonNull String permission) {
+ return AppOpsManager.permissionToOp(permission);
+ }
+
+ /**
+ * Retrieve an app op mode for an application.
+ *
+ * @param packageName the package name of the application
+ * @param appOp the name of the app op to retrieve
+ * @param context the {@code Context} to retrieve system services
+ *
+ * @return the app op mode for the application, or {@code null} if it cannot be retrieved
+ */
+ @Nullable
+ static Integer getAppOpMode(@NonNull String packageName, @NonNull String appOp,
+ @NonNull Context context) {
+ ApplicationInfo applicationInfo = Permissions.getApplicationInfo(packageName, context);
+ if (applicationInfo == null) {
+ return null;
+ }
+ AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+ return appOpsManager.unsafeCheckOpRaw(appOp, applicationInfo.uid, packageName);
+ }
+
+ /**
+ * Retrieve the default mode of an app op
+ *
+ * @param appOp the name of the app op to retrieve
+ *
+ * @return the default mode of the app op
+ */
+ static int getDefaultAppOpMode(@NonNull String appOp) {
+ return AppOpsManager.opToDefaultMode(appOp);
+ }
+
+ /**
+ * Set an app op mode for an application.
+ *
+ * @param packageName the package name of the application
+ * @param appOp the name of the app op to set
+ * @param mode the mode of the app op to set
+ * @param context the {@code Context} to retrieve system services
+ *
+ * @return whether app op mode is changed
+ */
+ static boolean setAppOpMode(@NonNull String packageName, @NonNull String appOp, int mode,
+ @NonNull Context context) {
+ Integer currentMode = getAppOpMode(packageName, appOp, context);
+ if (currentMode != null && currentMode == mode) {
+ return false;
+ }
+ ApplicationInfo applicationInfo = Permissions.getApplicationInfo(packageName, context);
+ if (applicationInfo == null) {
+ Log.e(LOG_TAG, "Cannot get ApplicationInfo for package to set app op mode: "
+ + packageName);
+ return false;
+ }
+ AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+ appOpsManager.setUidMode(appOp, applicationInfo.uid, mode);
+ return true;
+ }
+}