Allow device/profile owners to change app ops modes.

This allows them to continue to have this capability the
same as before we locked down access to it.

Bug: 78480444
Test: manual
Change-Id: If2b0722945235eb67676ace3f54efaa71a64bcde
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 7f26575..b06454c 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -21,6 +21,7 @@
 import android.app.ActivityThread;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.AppOpsManagerInternal;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -172,6 +173,9 @@
     final AtomicFile mFile;
     final Handler mHandler;
 
+    private final AppOpsManagerInternalImpl mAppOpsManagerInternal
+            = new AppOpsManagerInternalImpl();
+
     boolean mWriteScheduled;
     boolean mFastWriteScheduled;
     final Runnable mWriteRunner = new Runnable() {
@@ -200,6 +204,8 @@
      */
     private final ArrayMap<IBinder, ClientRestrictionState> mOpUserRestrictions = new ArrayMap<>();
 
+    SparseIntArray mProfileOwners;
+
     /**
      * All times are in milliseconds. These constants are kept synchronized with the system
      * global Settings. Any access to this class or its fields should be done while
@@ -554,6 +560,7 @@
     public void publish(Context context) {
         mContext = context;
         ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder());
+        LocalServices.addService(AppOpsManagerInternal.class, mAppOpsManagerInternal);
     }
 
     public void systemReady() {
@@ -923,12 +930,27 @@
         }
     }
 
+    void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid) {
+        if (callingPid == Process.myPid()) {
+            return;
+        }
+        final int callingUser = UserHandle.getUserId(callingUid);
+        synchronized (this) {
+            if (mProfileOwners != null && mProfileOwners.get(callingUser, -1) == callingUid) {
+                if (targetUid >= 0 && callingUser == UserHandle.getUserId(targetUid)) {
+                    // Profile owners are allowed to change modes but only for apps
+                    // within their user.
+                    return;
+                }
+            }
+        }
+        mContext.enforcePermission(android.Manifest.permission.MANAGE_APP_OPS_MODES,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
+    }
+
     @Override
     public void setUidMode(int code, int uid, int mode) {
-        if (Binder.getCallingPid() != Process.myPid()) {
-            mContext.enforcePermission(android.Manifest.permission.MANAGE_APP_OPS_MODES,
-                    Binder.getCallingPid(), Binder.getCallingUid(), null);
-        }
+        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
         verifyIncomingOp(code);
         code = AppOpsManager.opToSwitch(code);
 
@@ -1031,10 +1053,7 @@
 
     @Override
     public void setMode(int code, int uid, String packageName, int mode) {
-        if (Binder.getCallingPid() != Process.myPid()) {
-            mContext.enforcePermission(android.Manifest.permission.MANAGE_APP_OPS_MODES,
-                    Binder.getCallingPid(), Binder.getCallingUid(), null);
-        }
+        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
         verifyIncomingOp(code);
         ArraySet<ModeCallback> repCbs = null;
         code = AppOpsManager.opToSwitch(code);
@@ -1153,8 +1172,6 @@
     public void resetAllModes(int reqUserId, String reqPackageName) {
         final int callingPid = Binder.getCallingPid();
         final int callingUid = Binder.getCallingUid();
-        mContext.enforcePermission(android.Manifest.permission.MANAGE_APP_OPS_MODES,
-                callingPid, callingUid, null);
         reqUserId = ActivityManager.handleIncomingUser(callingPid, callingUid, reqUserId,
                 true, true, "resetAllModes", null);
 
@@ -1168,6 +1185,8 @@
             }
         }
 
+        enforceManageAppOpsModes(callingPid, callingUid, reqUid);
+
         HashMap<ModeCallback, ArrayList<ChangeRec>> callbacks = null;
         synchronized (this) {
             boolean changed = false;
@@ -1430,10 +1449,9 @@
     @Override
     public void setAudioRestriction(int code, int usage, int uid, int mode,
             String[] exceptionPackages) {
+        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
         verifyIncomingUid(uid);
         verifyIncomingOp(code);
-        mContext.enforcePermission(android.Manifest.permission.MANAGE_APP_OPS_MODES,
-                Binder.getCallingPid(), Binder.getCallingUid(), null);
         synchronized (this) {
             SparseArray<Restriction> usageRestrictions = mAudioRestrictions.get(code);
             if (usageRestrictions == null) {
@@ -2811,9 +2829,8 @@
                     return 0;
                 }
                 case "write-settings": {
-                    shell.mInternal.mContext.enforcePermission(
-                            android.Manifest.permission.MANAGE_APP_OPS_MODES,
-                            Binder.getCallingPid(), Binder.getCallingUid(), null);
+                    shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(),
+                            Binder.getCallingUid(), -1);
                     long token = Binder.clearCallingIdentity();
                     try {
                         synchronized (shell.mInternal) {
@@ -2827,9 +2844,8 @@
                     return 0;
                 }
                 case "read-settings": {
-                    shell.mInternal.mContext.enforcePermission(
-                            android.Manifest.permission.MANAGE_APP_OPS_MODES,
-                            Binder.getCallingPid(), Binder.getCallingUid(), null);
+                    shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(),
+                            Binder.getCallingUid(), -1);
                     long token = Binder.clearCallingIdentity();
                     try {
                         shell.mInternal.readState();
@@ -2991,6 +3007,17 @@
             final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
             final Date date = new Date();
             boolean needSep = false;
+            if (dumpOp < 0 && dumpMode < 0 && dumpPackage == null && mProfileOwners != null) {
+                pw.println("  Profile owners:");
+                for (int poi = 0; poi < mProfileOwners.size(); poi++) {
+                    pw.print("    User #");
+                    pw.print(mProfileOwners.keyAt(poi));
+                    pw.print(": ");
+                    UserHandle.formatUid(pw, mProfileOwners.valueAt(poi));
+                    pw.println();
+                }
+                pw.println();
+            }
             if (mOpModeWatchers.size() > 0) {
                 boolean printedHeader = false;
                 for (int i=0; i<mOpModeWatchers.size(); i++) {
@@ -3704,4 +3731,12 @@
             return true;
         }
     }
+
+    private final class AppOpsManagerInternalImpl extends AppOpsManagerInternal {
+        @Override public void setDeviceAndProfileOwners(SparseIntArray owners) {
+            synchronized (AppOpsService.this) {
+                mProfileOwners = owners;
+            }
+        }
+    }
 }