Merge "Allow device/profile owners to change app ops modes." into pi-dev
diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java
new file mode 100644
index 0000000..24c5d23
--- /dev/null
+++ b/core/java/android/app/AppOpsManagerInternal.java
@@ -0,0 +1,33 @@
+/*
+ * 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 android.app;
+
+import android.util.SparseIntArray;
+
+/**
+ * App ops service local interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class AppOpsManagerInternal {
+    /**
+     * Set the currently configured device and profile owners.  Specifies the package uid (value)
+     * that has been configured for each user (key) that has one.  These will be allowed privileged
+     * access to app ops for their user.
+     */
+    public abstract void setDeviceAndProfileOwners(SparseIntArray owners);
+}
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 169f2a8..4c914c2 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
@@ -551,6 +557,7 @@
     public void publish(Context context) {
         mContext = context;
         ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder());
+        LocalServices.addService(AppOpsManagerInternal.class, mAppOpsManagerInternal);
     }
 
     public void systemReady() {
@@ -921,12 +928,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);
 
@@ -1029,10 +1051,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);
@@ -1151,8 +1170,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);
 
@@ -1166,6 +1183,8 @@
             }
         }
 
+        enforceManageAppOpsModes(callingPid, callingUid, reqUid);
+
         HashMap<ModeCallback, ArrayList<ChangeRec>> callbacks = null;
         synchronized (this) {
             boolean changed = false;
@@ -1428,10 +1447,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) {
@@ -2809,9 +2827,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) {
@@ -2825,9 +2842,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();
@@ -2989,6 +3005,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++) {
@@ -3702,4 +3729,12 @@
             return true;
         }
     }
+
+    private final class AppOpsManagerInternalImpl extends AppOpsManagerInternal {
+        @Override public void setDeviceAndProfileOwners(SparseIntArray owners) {
+            synchronized (AppOpsService.this) {
+                mProfileOwners = owners;
+            }
+        }
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 729dba5..c370a00 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3351,6 +3351,7 @@
             case SystemService.PHASE_LOCK_SETTINGS_READY:
                 onLockSettingsReady();
                 loadAdminDataAsync();
+                mOwners.systemReady();
                 break;
             case SystemService.PHASE_BOOT_COMPLETED:
                 ensureDeviceOwnerUserStarted(); // TODO Consider better place to do this.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 8366114..632f0aa 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -17,11 +17,14 @@
 package com.android.server.devicepolicy;
 
 import android.annotation.Nullable;
+import android.app.AppOpsManagerInternal;
 import android.app.admin.SystemUpdateInfo;
 import android.app.admin.SystemUpdatePolicy;
 import android.content.ComponentName;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
+import android.os.Binder;
 import android.os.Environment;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -32,10 +35,12 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.util.Xml;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FastXmlSerializer;
+import com.android.server.LocalServices;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -99,6 +104,8 @@
     private final UserManagerInternal mUserManagerInternal;
     private final PackageManagerInternal mPackageManagerInternal;
 
+    private boolean mSystemReady;
+
     // Internal state for the device owner package.
     private OwnerInfo mDeviceOwner;
 
@@ -179,6 +186,7 @@
                         getDeviceOwnerUserId()));
             }
             pushToPackageManagerLocked();
+            pushToAppOpsLocked();
         }
     }
 
@@ -262,6 +270,7 @@
 
             mUserManagerInternal.setDeviceManaged(true);
             pushToPackageManagerLocked();
+            pushToAppOpsLocked();
         }
     }
 
@@ -272,6 +281,7 @@
 
             mUserManagerInternal.setDeviceManaged(false);
             pushToPackageManagerLocked();
+            pushToAppOpsLocked();
         }
     }
 
@@ -283,6 +293,7 @@
                     /* remoteBugreportHash =*/ null));
             mUserManagerInternal.setUserManaged(userId, true);
             pushToPackageManagerLocked();
+            pushToAppOpsLocked();
         }
     }
 
@@ -291,6 +302,7 @@
             mProfileOwners.remove(userId);
             mUserManagerInternal.setUserManaged(userId, false);
             pushToPackageManagerLocked();
+            pushToAppOpsLocked();
         }
     }
 
@@ -302,6 +314,7 @@
                     ownerInfo.remoteBugreportHash);
             mProfileOwners.put(userId, newOwnerInfo);
             pushToPackageManagerLocked();
+            pushToAppOpsLocked();
         }
     }
 
@@ -313,6 +326,7 @@
                     mDeviceOwner.userRestrictionsMigrated, mDeviceOwner.remoteBugreportUri,
                     mDeviceOwner.remoteBugreportHash);
             pushToPackageManagerLocked();
+            pushToAppOpsLocked();
         }
     }
 
@@ -581,6 +595,48 @@
         }
     }
 
+    void pushToAppOpsLocked() {
+        if (!mSystemReady) {
+            return;
+        }
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            final SparseIntArray owners = new SparseIntArray();
+            if (mDeviceOwner != null) {
+                final int uid = mPackageManagerInternal.getPackageUid(mDeviceOwner.packageName,
+                        PackageManager.MATCH_ALL | PackageManager.MATCH_KNOWN_PACKAGES,
+                        mDeviceOwnerUserId);
+                if (uid >= 0) {
+                    owners.put(mDeviceOwnerUserId, uid);
+                }
+            }
+            if (mProfileOwners != null) {
+                for (int poi = mProfileOwners.size() - 1; poi >= 0; poi--) {
+                    final int uid = mPackageManagerInternal.getPackageUid(
+                            mProfileOwners.valueAt(poi).packageName,
+                            PackageManager.MATCH_ALL | PackageManager.MATCH_KNOWN_PACKAGES,
+                            mProfileOwners.keyAt(poi));
+                    if (uid >= 0) {
+                        owners.put(mProfileOwners.keyAt(poi), uid);
+                    }
+                }
+            }
+            AppOpsManagerInternal appops = LocalServices.getService(AppOpsManagerInternal.class);
+            if (appops != null) {
+                appops.setDeviceAndProfileOwners(owners.size() > 0 ? owners : null);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    public void systemReady() {
+        synchronized (mLock) {
+            mSystemReady = true;
+            pushToAppOpsLocked();
+        }
+    }
+
     private abstract static class FileReadWriter {
         private final File mFile;