Make UserManager enforce user restrictions, not DPM.

- Now even if a user restriction is set via UserManager, it'll be correctly
enforced.

- Changed the way AudioService enforces the OP_MUTE_MICROPHONE and
OP_AUDIO_MASTER_VOLUME app ops -- previously, when they're set, even a muting
call would be rejected.  This was why DPMS.setUserRestriction() used different
calling orders for DISALLOW_UNMUTE_MICROPHONE/DISALLOW_ADJUST_VOLUME depending
on setting them or clearing them.
Now, even when the app ops are set, we still allow muting calls.

Bug 23902097
Bug 24981972

Change-Id: I865b5de43e15f5955f94006475a5ec6254904d31
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 66e731a..d89f47c 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1756,7 +1756,8 @@
         if (uid == android.os.Process.SYSTEM_UID) {
             uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
         }
-        if (mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage)
+        // If OP_AUDIO_MASTER_VOLUME is set, disallow unmuting.
+        if (!mute && mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage)
                 != AppOpsManager.MODE_ALLOWED) {
             return;
         }
@@ -1848,7 +1849,8 @@
         if (uid == android.os.Process.SYSTEM_UID) {
             uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
         }
-        if (mAppOps.noteOp(AppOpsManager.OP_MUTE_MICROPHONE, uid, callingPackage)
+        // If OP_MUTE_MICROPHONE is set, disallow unmuting.
+        if (!on && mAppOps.noteOp(AppOpsManager.OP_MUTE_MICROPHONE, uid, callingPackage)
                 != AppOpsManager.MODE_ALLOWED) {
             return;
         }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c1f827f..62ced52 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -199,6 +199,14 @@
     @GuardedBy("mRestrictionsLock")
     private final SparseArray<Bundle> mCachedEffectiveUserRestrictions = new SparseArray<>();
 
+    /**
+     * User restrictions that have already been applied in {@link #applyUserRestrictionsRL}.  We
+     * use it to detect restrictions that have changed since the last
+     * {@link #applyUserRestrictionsRL} call.
+     */
+    @GuardedBy("mRestrictionsLock")
+    private final SparseArray<Bundle> mAppliedUserRestrictions = new SparseArray<>();
+
     private final Bundle mGuestRestrictions = new Bundle();
 
     /**
@@ -727,8 +735,6 @@
             Log.d(LOG_TAG, "updateUserRestrictionsInternalLocked userId=" + userId
                     + " bundle=" + newRestrictions);
         }
-        final Bundle prevRestrictions = getEffectiveUserRestrictions(userId);
-
         // Update system restrictions.
         if (newRestrictions != null) {
             // If newRestrictions == the current one, it's probably a bug.
@@ -742,12 +748,18 @@
 
         mCachedEffectiveUserRestrictions.put(userId, effective);
 
-        applyUserRestrictionsRL(userId, effective, prevRestrictions);
+        applyUserRestrictionsRL(userId, effective);
     }
 
     @GuardedBy("mRestrictionsLock")
-    private void applyUserRestrictionsRL(int userId,
-            Bundle newRestrictions, Bundle prevRestrictions) {
+    private void applyUserRestrictionsRL(int userId, Bundle newRestrictions) {
+        final Bundle prevRestrictions = mAppliedUserRestrictions.get(userId);
+
+        if (DBG) {
+            Log.d(LOG_TAG, "applyUserRestrictionsRL userId=" + userId
+                    + " new=" + newRestrictions + " prev=" + prevRestrictions);
+        }
+
         final long token = Binder.clearCallingIdentity();
         try {
             mAppOpsService.setUserRestrictions(newRestrictions, userId);
@@ -757,7 +769,10 @@
             Binder.restoreCallingIdentity(token);
         }
 
-        // TODO Move the code from DPMS.setUserRestriction().
+        UserRestrictionsUtils.applyUserRestrictions(
+                mContext, userId, newRestrictions, prevRestrictions);
+
+        mAppliedUserRestrictions.put(userId, new Bundle(newRestrictions));
     }
 
     @GuardedBy("mRestrictionsLock")
@@ -768,9 +783,8 @@
     @GuardedBy("mRestrictionsLock")
     private void updateEffectiveUserRestrictionsForAllUsersRL() {
         // First, invalidate all cached values.
-        synchronized (mRestrictionsLock) {
-            mCachedEffectiveUserRestrictions.clear();
-        }
+        mCachedEffectiveUserRestrictions.clear();
+
         // We don't want to call into ActivityManagerNative while taking a lock, so we'll call
         // it on a handler.
         final Runnable r = new Runnable() {
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 23e3b35..28df9f6 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -18,10 +18,19 @@
 
 import com.google.android.collect.Sets;
 
-import com.android.internal.util.Preconditions;
-
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.media.IAudioService;
+import android.net.Uri;
+import android.os.Binder;
 import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.Slog;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlSerializer;
@@ -31,6 +40,8 @@
 import java.util.Set;
 
 public class UserRestrictionsUtils {
+    private static final String TAG = "UserRestrictionsUtils";
+
     private UserRestrictionsUtils() {
     }
 
@@ -115,6 +126,118 @@
         }
     }
 
+    /**
+     * Takes a new use restriction set and the previous set, and apply the restrictions that have
+     * changed.
+     */
+    public static void applyUserRestrictions(Context context, int userId,
+            @Nullable Bundle newRestrictions, @Nullable Bundle prevRestrictions) {
+        if (newRestrictions == null) {
+            newRestrictions = Bundle.EMPTY;
+        }
+        if (prevRestrictions == null) {
+            prevRestrictions = Bundle.EMPTY;
+        }
+        for (String key : USER_RESTRICTIONS) {
+            final boolean newValue = newRestrictions.getBoolean(key);
+            final boolean prevValue = prevRestrictions.getBoolean(key);
+
+            if (newValue != prevValue) {
+                applyUserRestriction(context, userId, key, newValue);
+            }
+        }
+    }
+
+    private static void applyUserRestriction(Context context, int userId, String key,
+            boolean newValue) {
+        // When certain restrictions are cleared, we don't update the system settings,
+        // because these settings are changeable on the Settings UI and we don't know the original
+        // value -- for example LOCATION_MODE might have been off already when the restriction was
+        // set, and in that case even if the restriction is lifted, changing it to ON would be
+        // wrong.  So just don't do anything in such a case.  If the user hopes to enable location
+        // later, they can do it on the Settings UI.
+
+        final ContentResolver cr = context.getContentResolver();
+        final long id = Binder.clearCallingIdentity();
+        try {
+            switch (key) {
+                case UserManager.DISALLOW_UNMUTE_MICROPHONE:
+                    IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE))
+                            .setMicrophoneMute(newValue, context.getPackageName(), userId);
+                    break;
+                case UserManager.DISALLOW_ADJUST_VOLUME:
+                    IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE))
+                            .setMasterMute(newValue, 0, context.getPackageName(), userId);
+                    break;
+                case UserManager.DISALLOW_CONFIG_WIFI:
+                    if (newValue) {
+                        android.provider.Settings.Secure.putIntForUser(cr,
+                                android.provider.Settings.Secure
+                                        .WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0, userId);
+                    }
+                    break;
+                case UserManager.DISALLOW_SHARE_LOCATION:
+                    if (newValue) {
+                        android.provider.Settings.Secure.putIntForUser(cr,
+                                android.provider.Settings.Secure.LOCATION_MODE,
+                                android.provider.Settings.Secure.LOCATION_MODE_OFF,
+                                userId);
+                        android.provider.Settings.Secure.putStringForUser(cr,
+                                android.provider.Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "",
+                                userId);
+                    }
+                    // Send out notifications as some clients may want to reread the
+                    // value which actually changed due to a restriction having been
+                    // applied.
+                    final String property =
+                            android.provider.Settings.Secure.SYS_PROP_SETTING_VERSION;
+                    long version = SystemProperties.getLong(property, 0) + 1;
+                    SystemProperties.set(property, Long.toString(version));
+
+                    final String name = android.provider.Settings.Secure.LOCATION_PROVIDERS_ALLOWED;
+                    final Uri url = Uri.withAppendedPath(
+                            android.provider.Settings.Secure.CONTENT_URI, name);
+                    context.getContentResolver().notifyChange(url, null, true, userId);
+
+                    break;
+                case UserManager.DISALLOW_DEBUGGING_FEATURES:
+                    if (newValue) {
+                        // Only disable adb if changing for system user, since it is global
+                        // TODO: should this be admin user?
+                        if (userId == UserHandle.USER_SYSTEM) {
+                            android.provider.Settings.Global.putStringForUser(cr,
+                                    android.provider.Settings.Global.ADB_ENABLED, "0",
+                                    userId);
+                        }
+                    }
+                    break;
+                case UserManager.ENSURE_VERIFY_APPS:
+                    if (newValue) {
+                        android.provider.Settings.Global.putStringForUser(
+                                context.getContentResolver(),
+                                android.provider.Settings.Global.PACKAGE_VERIFIER_ENABLE, "1",
+                                userId);
+                        android.provider.Settings.Global.putStringForUser(
+                                context.getContentResolver(),
+                                android.provider.Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, "1",
+                                userId);
+                    }
+                    break;
+                case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES:
+                    if (newValue) {
+                        android.provider.Settings.Secure.putIntForUser(cr,
+                                android.provider.Settings.Secure.INSTALL_NON_MARKET_APPS, 0,
+                                userId);
+                    }
+                    break;
+            }
+        } catch (RemoteException re) {
+            Slog.e(TAG, "Failed to talk to AudioService.", re);
+        } finally {
+            Binder.restoreCallingIdentity(id);
+        }
+    }
+
     public static void dumpRestrictions(PrintWriter pw, String prefix, Bundle restrictions) {
         boolean noneSet = true;
         if (restrictions != null) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2e7f609..1602c12 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -5618,9 +5618,6 @@
 
                 final long id = mInjector.binderClearCallingIdentity();
                 try {
-                    // Original value.
-                    final boolean alreadyRestricted = mUserManager.hasUserRestriction(key, user);
-
                     // Save the restriction to ActiveAdmin.
                     // TODO When DO sets a restriction, it'll always be treated as device-wide.
                     // If there'll be a policy that can be set by both, we'll need scoping support,
@@ -5629,85 +5626,12 @@
                     activeAdmin.ensureUserRestrictions().putBoolean(key, enabledFromThisOwner);
                     saveSettingsLocked(userHandle);
 
-                    // Tell UserManager the new value.  Note this needs to be done before calling
-                    // into AudioService, because AS will check AppOps that'll be updated by UM.
+                    // Tell UserManager the new value.
                     if (isDeviceOwner) {
                         mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersRL();
                     } else {
                         mUserManagerInternal.updateEffectiveUserRestrictionsRL(userHandle);
                     }
-
-                    // New value.
-                    final boolean enabled = mUserManager.hasUserRestriction(key, user);
-
-                    // TODO The rest of the code should move to UserManagerService.
-
-                    if (enabled && !alreadyRestricted) {
-                        if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
-                            mInjector.getIAudioService()
-                                    .setMicrophoneMute(true, mContext.getPackageName(), userHandle);
-                        } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
-                            mInjector.getIAudioService()
-                                    .setMasterMute(true, 0, mContext.getPackageName(), userHandle);
-                        } else if (UserManager.DISALLOW_CONFIG_WIFI.equals(key)) {
-                            mInjector.settingsSecurePutIntForUser(
-                                    Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0,
-                                    userHandle);
-                        } else if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
-                            mInjector.settingsSecurePutIntForUser(
-                                    Settings.Secure.LOCATION_MODE,
-                                    Settings.Secure.LOCATION_MODE_OFF,
-                                    userHandle);
-                            mInjector.settingsSecurePutStringForUser(
-                                    Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "",
-                                    userHandle);
-                        } else if (UserManager.DISALLOW_DEBUGGING_FEATURES.equals(key)) {
-                            // Only disable adb if changing for system user, since it is global
-                            // TODO: should this be admin user?
-                            if (userHandle == UserHandle.USER_SYSTEM) {
-                                mInjector.settingsGlobalPutStringForUser(
-                                        Settings.Global.ADB_ENABLED, "0", userHandle);
-                            }
-                        } else if (UserManager.ENSURE_VERIFY_APPS.equals(key)) {
-                            mInjector.settingsGlobalPutStringForUser(
-                                    Settings.Global.PACKAGE_VERIFIER_ENABLE, "1",
-                                    userHandle);
-                            mInjector.settingsGlobalPutStringForUser(
-                                    Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, "1",
-                                    userHandle);
-                        } else if (UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES.equals(key)) {
-                            mInjector.settingsSecurePutIntForUser(
-                                    Settings.Secure.INSTALL_NON_MARKET_APPS, 0,
-                                    userHandle);
-                        }
-                    }
-
-                    if (enabled != alreadyRestricted) {
-                        if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
-                            // Send out notifications however as some clients may want to reread the
-                            // value which actually changed due to a restriction having been
-                            // applied.
-                            final String property = Settings.Secure.SYS_PROP_SETTING_VERSION;
-                            long version = mInjector.systemPropertiesGetLong(property, 0) + 1;
-                            mInjector.systemPropertiesSet(property, Long.toString(version));
-
-                            final String name = Settings.Secure.LOCATION_PROVIDERS_ALLOWED;
-                            Uri url = Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name);
-                            mContext.getContentResolver().notifyChange(url, null, true, userHandle);
-                        }
-                    }
-                    if (!enabled && alreadyRestricted) {
-                        if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
-                            mInjector.getIAudioService()
-                                    .setMicrophoneMute(false, mContext.getPackageName(),
-                                            userHandle);
-                        } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
-                            mInjector.getIAudioService()
-                                    .setMasterMute(false, 0, mContext.getPackageName(), userHandle);
-                        }
-                    }
-                } catch (RemoteException re) {
-                    Slog.e(LOG_TAG, "Failed to talk to AudioService.", re);
                 } finally {
                     mInjector.binderRestoreCallingIdentity(id);
                 }