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;
+    }
+}