Runtime permissions cannot be set on legacy apps by device policy

Clarify docs that runtime permissions can be granted or revoked by
a profile owner/device owner only for MNC apps and not legacy apps.

Check the targetSdkVersion and return false if legacy app.

Remove all policy flags from permissions when cleaning up
a device or profile owner.

Bug: 21835304
Bug: 21889278
Change-Id: I4271394737990983449048d112a1830f9d0f2d78
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index b859dca..4d1cff5 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -4350,6 +4350,12 @@
      * group that the runtime permission belongs to. This method can only be called
      * by a profile or device owner.
      *
+     * <p/>Setting the grant state to {@link #PERMISSION_GRANT_STATE_DEFAULT default} does not
+     * revoke the permission. It retains the previous grant, if any.
+     *
+     * <p/>Permissions can be granted or revoked only for applications built with a
+     * {@code targetSdkVersion} of {@link android.os.Build.VERSION_CODES#MNC} or later.
+     *
      * @param admin Which profile or device owner this request is associated with.
      * @param packageName The application to grant or revoke a permission to.
      * @param permission The permission to grant or revoke.
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 2dbcde9..34e4701 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -106,6 +106,8 @@
     void updatePermissionFlags(String permissionName, String packageName, int flagMask,
             int flagValues, int userId);
 
+    void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId);
+
     boolean shouldShowRequestPermissionRationale(String permissionName,
             String packageName, int userId);
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 41d3ffd..7a39c2b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3354,11 +3354,8 @@
         enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
                 "updatePermissionFlags");
 
-        // Only the system can change policy and system fixed flags.
+        // Only the system can change system fixed flags.
         if (getCallingUid() != Process.SYSTEM_UID) {
-            flagMask &= ~PackageManager.FLAG_PERMISSION_POLICY_FIXED;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_POLICY_FIXED;
-
             flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
             flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
         }
@@ -3387,18 +3384,63 @@
                 return;
             }
 
+            boolean hadState = permissionsState.getRuntimePermissionState(name, userId) != null;
+
             if (permissionsState.updatePermissionFlags(bp, userId, flagMask, flagValues)) {
                 // Install and runtime permissions are stored in different places,
                 // so figure out what permission changed and persist the change.
                 if (permissionsState.getInstallPermissionState(name) != null) {
                     scheduleWriteSettingsLocked();
-                } else if (permissionsState.getRuntimePermissionState(name, userId) != null) {
+                } else if (permissionsState.getRuntimePermissionState(name, userId) != null
+                        || hadState) {
                     mSettings.writeRuntimePermissionsForUserLPr(userId, false);
                 }
             }
         }
     }
 
+    /**
+     * Update the permission flags for all packages and runtime permissions of a user in order
+     * to allow device or profile owner to remove POLICY_FIXED.
+     */
+    @Override
+    public void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId) {
+        if (!sUserManager.exists(userId)) {
+            return;
+        }
+
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.GRANT_REVOKE_PERMISSIONS,
+                "updatePermissionFlagsForAllApps");
+
+        enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
+                "updatePermissionFlagsForAllApps");
+
+        // Only the system can change system fixed flags.
+        if (getCallingUid() != Process.SYSTEM_UID) {
+            flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+        }
+
+        synchronized (mPackages) {
+            boolean changed = false;
+            final int packageCount = mPackages.size();
+            for (int pkgIndex = 0; pkgIndex < packageCount; pkgIndex++) {
+                final PackageParser.Package pkg = mPackages.valueAt(pkgIndex);
+                SettingBase sb = (SettingBase) pkg.mExtras;
+                if (sb == null) {
+                    continue;
+                }
+                PermissionsState permissionsState = sb.getPermissionsState();
+                changed |= permissionsState.updatePermissionFlagsForAllPermissions(
+                        userId, flagMask, flagValues);
+            }
+            if (changed) {
+                mSettings.writeRuntimePermissionsForUserLPr(userId, false);
+            }
+        }
+    }
+
     @Override
     public boolean shouldShowRequestPermissionRationale(String permissionName,
             String packageName, int userId) {
diff --git a/services/core/java/com/android/server/pm/PermissionsState.java b/services/core/java/com/android/server/pm/PermissionsState.java
index ad662be..04beafd 100644
--- a/services/core/java/com/android/server/pm/PermissionsState.java
+++ b/services/core/java/com/android/server/pm/PermissionsState.java
@@ -344,6 +344,22 @@
         return permissionData.updateFlags(userId, flagMask, flagValues);
     }
 
+    public boolean updatePermissionFlagsForAllPermissions(
+            int userId, int flagMask, int flagValues) {
+        enforceValidUserId(userId);
+
+        if (mPermissions == null) {
+            return false;
+        }
+        boolean changed = false;
+        final int permissionCount = mPermissions.size();
+        for (int i = 0; i < permissionCount; i++) {
+            PermissionData permissionData = mPermissions.valueAt(i);
+            changed |= permissionData.updateFlags(userId, flagMask, flagValues);
+        }
+        return changed;
+    }
+
     /**
      * Compute the Linux gids for a given device user from the permissions
      * granted to this user. Note that these are computed to avoid additional
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 74adc6b..e44a7ab87 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4217,11 +4217,15 @@
             long ident = Binder.clearCallingIdentity();
             try {
                 clearUserRestrictions(new UserHandle(UserHandle.USER_OWNER));
+                AppGlobals.getPackageManager().updatePermissionFlagsForAllApps(
+                        PackageManager.FLAG_PERMISSION_POLICY_FIXED,
+                        0, UserHandle.USER_OWNER);
                 if (mDeviceOwner != null) {
                     mDeviceOwner.clearDeviceOwner();
                     mDeviceOwner.writeOwnerFile();
                     updateDeviceOwnerLocked();
                 }
+            } catch (RemoteException re) {
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -4388,10 +4392,14 @@
             long ident = Binder.clearCallingIdentity();
             try {
                 clearUserRestrictions(callingUser);
+                AppGlobals.getPackageManager().updatePermissionFlagsForAllApps(
+                        PackageManager.FLAG_PERMISSION_POLICY_FIXED,
+                        0, callingUser.getIdentifier());
                 if (mDeviceOwner != null) {
                     mDeviceOwner.removeProfileOwner(userId);
                     mDeviceOwner.writeOwnerFile();
                 }
+            } catch (RemoteException re) {
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -6390,21 +6398,27 @@
             getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
             long ident = Binder.clearCallingIdentity();
             try {
-                PackageManager packageManager = mContext.getPackageManager();
+                final ApplicationInfo ai = AppGlobals.getPackageManager()
+                        .getApplicationInfo(packageName, 0, user.getIdentifier());
+                final int targetSdkVersion = ai == null ? 0 : ai.targetSdkVersion;
+                if (targetSdkVersion < android.os.Build.VERSION_CODES.MNC) {
+                    return false;
+                }
+                final PackageManager packageManager = mContext.getPackageManager();
                 switch (grantState) {
                     case DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED: {
+                        packageManager.grantRuntimePermission(packageName, permission, user);
                         packageManager.updatePermissionFlags(permission, packageName,
                                 PackageManager.FLAG_PERMISSION_POLICY_FIXED,
                                 PackageManager.FLAG_PERMISSION_POLICY_FIXED, user);
-                        packageManager.grantRuntimePermission(packageName, permission, user);
                     } break;
 
                     case DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED: {
+                        packageManager.revokeRuntimePermission(packageName,
+                                permission, user);
                         packageManager.updatePermissionFlags(permission, packageName,
                                 PackageManager.FLAG_PERMISSION_POLICY_FIXED,
                                 PackageManager.FLAG_PERMISSION_POLICY_FIXED, user);
-                        packageManager.revokeRuntimePermission(packageName,
-                                permission, user);
                     } break;
 
                     case DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT: {