More work on layered user restrictions.

- Now when DO/PO sets a user restriction, DPMS pushes it to UMS and
then UMS persists it, in order for UserManager.hasUserRestriction()
to never have to talk with DPMS, which would cause lock inversion.

- Also apply user restrictions when a user start.

- This is an updated version of the abandoned CL -- the difference
is, ActivityManager no longer has to call DPMS.

- Also removed an unnecessary write to userlist.xml in UMS.
upgradeIfNecessaryLP().

Bug 23902097
Bug 25388912
Bug 25354031
Bug 25641040

Change-Id: I0948aea06ad7d0f45fe612a431d765faddfe3c58
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 4270e16..0a0d77d 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -16,8 +16,6 @@
 
 package android.app.admin;
 
-import android.os.Bundle;
-
 import java.util.List;
 
 /**
@@ -71,13 +69,4 @@
      * @return true if the uid is an active admin with the given policy.
      */
     public abstract boolean isActiveAdminWithPolicy(int uid, int reqPolicy);
-
-    /**
-     * Takes a {@link Bundle} containing "base" user restrictions stored in
-     * {@link com.android.server.pm.UserManagerService}, mixes restrictions set by the device owner
-     * and the profile owner and returns the merged restrictions.
-     *
-     * This method always returns a new {@link Bundle}.
-     */
-    public abstract Bundle getComposedUserRestrictions(int userId, Bundle inBundle);
 }
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
index 9178ec6..26c7a1e 100644
--- a/core/java/android/os/UserManagerInternal.java
+++ b/core/java/android/os/UserManagerInternal.java
@@ -15,6 +15,9 @@
  */
 package android.os;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 /**
  * @hide Only for use within the system server.
  */
@@ -31,32 +34,18 @@
     }
 
     /**
-     * Lock that must be held when calling certain methods in this class.
+     * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService}
+     * to set per-user as well as global user restrictions.
      *
-     * This is used to avoid dead lock between
-     * {@link com.android.server.pm.UserManagerService} and
-     * {@link com.android.server.devicepolicy.DevicePolicyManagerService}.  This lock should not
-     * be newly taken while holding the DPMS lock, which would cause a dead lock.  Take this
-     * lock first before taking the DPMS lock to avoid that.
+     * @param userId target user id for the local restrictions.
+     * @param localRestrictions per-user restrictions.
+     *     Caller must not change it once passed to this method.
+     * @param globalRestrictions global restrictions set by DO.  Must be null when PO changed user
+     *     restrictions, in which case global restrictions won't change.
+     *     Caller must not change it once passed to this method.
      */
-    public abstract Object getUserRestrictionsLock();
-
-    /**
-     * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to get
-     * {@link com.android.server.pm.UserManagerService} to update effective user restrictions.
-     *
-     * Must be called while taking the {@link #getUserRestrictionsLock()} lock.
-     */
-    public abstract void updateEffectiveUserRestrictionsLR(int userId);
-
-    /**
-     * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to get
-     * {@link com.android.server.pm.UserManagerService} to update effective user restrictions.
-     *
-     * Must be called while taking the {@link #getUserRestrictionsLock()} lock.
-     */
-    public abstract void updateEffectiveUserRestrictionsForAllUsersLR();
-
+    public abstract void setDevicePolicyUserRestrictions(int userId,
+            @NonNull Bundle localRestrictions, @Nullable Bundle globalRestrictions);
     /**
      * Returns the "base" user restrictions.
      *
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index d6fced6..76403a7 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -519,6 +519,9 @@
                 }
 
                 if (uss.mState == UserState.STATE_BOOTING) {
+                    // Let user manager propagate user restrictions to other services.
+                    getUserManager().onBeforeStartUser(userId);
+
                     // Booting up a new user, need to tell system services about it.
                     // Note that this is on the same handler as scheduling of broadcasts,
                     // which is important because it needs to go first.
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index ab0b182..e44e63c 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -25,7 +25,6 @@
 import android.app.IActivityManager;
 import android.app.IStopUserCallback;
 import android.app.admin.DevicePolicyManager;
-import android.app.admin.DevicePolicyManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -36,6 +35,7 @@
 import android.graphics.Bitmap;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.Debug;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
@@ -48,7 +48,6 @@
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.ShellCommand;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManagerInternal;
@@ -104,7 +103,8 @@
  */
 public class UserManagerService extends IUserManager.Stub {
     private static final String LOG_TAG = "UserManagerService";
-    private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
+    static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
+    private static final boolean DBG_WITH_STACKTRACE = false; // DO NOT SUBMIT WITH TRUE
 
     private static final String TAG_NAME = "name";
     private static final String ATTR_FLAGS = "flags";
@@ -123,6 +123,7 @@
     private static final String TAG_USERS = "users";
     private static final String TAG_USER = "user";
     private static final String TAG_RESTRICTIONS = "restrictions";
+    private static final String TAG_DEVICE_POLICY_RESTRICTIONS = "device_policy_restrictions";
     private static final String TAG_ENTRY = "entry";
     private static final String TAG_VALUE = "value";
     private static final String ATTR_KEY = "key";
@@ -206,13 +207,27 @@
     private final SparseArray<Bundle> mCachedEffectiveUserRestrictions = new SparseArray<>();
 
     /**
-     * User restrictions that have already been applied in {@link #applyUserRestrictionsLR}.  We
-     * use it to detect restrictions that have changed since the last
-     * {@link #applyUserRestrictionsLR} call.
+     * User restrictions that have already been applied in
+     * {@link #updateUserRestrictionsInternalLR(Bundle, int)}.  We use it to detect restrictions
+     * that have changed since the last
+     * {@link #updateUserRestrictionsInternalLR(Bundle, int)} call.
      */
     @GuardedBy("mRestrictionsLock")
     private final SparseArray<Bundle> mAppliedUserRestrictions = new SparseArray<>();
 
+    /**
+     * User restrictions set by {@link DevicePolicyManager} that should be applied to all users,
+     * including guests.
+     */
+    @GuardedBy("mRestrictionsLock")
+    private Bundle mDevicePolicyGlobalUserRestrictions;
+
+    /**
+     * User restrictions set by {@link DevicePolicyManager} for each user.
+     */
+    @GuardedBy("mRestrictionsLock")
+    private final SparseArray<Bundle> mDevicePolicyLocalUserRestrictions = new SparseArray<>();
+
     @GuardedBy("mGuestRestrictions")
     private final Bundle mGuestRestrictions = new Bundle();
 
@@ -302,16 +317,14 @@
                     + " (name=" + ui.name + ")");
             removeUserState(ui.id);
         }
+
         onUserForeground(UserHandle.USER_SYSTEM);
+
         mAppOpsService = IAppOpsService.Stub.asInterface(
                 ServiceManager.getService(Context.APP_OPS_SERVICE));
-        for (int i = 0; i < mUserIds.length; ++i) {
-            final int userId = mUserIds[i];
-            try {
-                mAppOpsService.setUserRestrictions(getEffectiveUserRestrictions(userId), userId);
-            } catch (RemoteException e) {
-                Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
-            }
+
+        synchronized (mRestrictionsLock) {
+            applyUserRestrictionsLR(UserHandle.USER_SYSTEM);
         }
     }
 
@@ -661,19 +674,72 @@
         }
     }
 
+    /**
+     * See {@link UserManagerInternal#setDevicePolicyUserRestrictions(int, Bundle, Bundle)}
+     */
+    void setDevicePolicyUserRestrictions(int userId, @NonNull Bundle local,
+            @Nullable Bundle global) {
+        Preconditions.checkNotNull(local);
+        boolean globalChanged = false;
+        boolean localChanged;
+        synchronized (mRestrictionsLock) {
+            if (global != null) {
+                // Update global.
+                globalChanged = !UserRestrictionsUtils.areEqual(
+                        mDevicePolicyGlobalUserRestrictions, global);
+                if (globalChanged) {
+                    mDevicePolicyGlobalUserRestrictions = global;
+                }
+            }
+            {
+                // Update local.
+                final Bundle prev = mDevicePolicyLocalUserRestrictions.get(userId);
+                localChanged = !UserRestrictionsUtils.areEqual(prev, local);
+                if (localChanged) {
+                    mDevicePolicyLocalUserRestrictions.put(userId, local);
+                }
+            }
+        }
+        if (DBG) {
+            Log.d(LOG_TAG, "setDevicePolicyUserRestrictions: userId=" + userId
+                            + " global=" + global + (globalChanged ? " (changed)" : "")
+                            + " local=" + local + (localChanged ? " (changed)" : "")
+            );
+        }
+        // Don't call them within the mRestrictionsLock.
+        synchronized (mPackagesLock) {
+            if (globalChanged) {
+                writeUserListLP();
+            }
+            if (localChanged) {
+                writeUserLP(getUserInfoNoChecks(userId));
+            }
+        }
+
+        synchronized (mRestrictionsLock) {
+            if (globalChanged) {
+                applyUserRestrictionsForAllUsersLR();
+            } else if (localChanged) {
+                applyUserRestrictionsLR(userId);
+            }
+        }
+    }
+
     @GuardedBy("mRestrictionsLock")
     private Bundle computeEffectiveUserRestrictionsLR(int userId) {
-        final DevicePolicyManagerInternal dpmi =
-                LocalServices.getService(DevicePolicyManagerInternal.class);
-        final Bundle systemRestrictions = mBaseUserRestrictions.get(userId);
+        final Bundle baseRestrictions =
+                UserRestrictionsUtils.nonNull(mBaseUserRestrictions.get(userId));
+        final Bundle global = mDevicePolicyGlobalUserRestrictions;
+        final Bundle local = mDevicePolicyLocalUserRestrictions.get(userId);
 
-        final Bundle effective;
-        if (dpmi == null) {
-            // TODO Make sure it's because DPMS is disabled and not because we called it too early.
-            effective = systemRestrictions;
-        } else {
-            effective = dpmi.getComposedUserRestrictions(userId, systemRestrictions);
+        if (UserRestrictionsUtils.isEmpty(global) && UserRestrictionsUtils.isEmpty(local)) {
+            // Common case first.
+            return baseRestrictions;
         }
+        final Bundle effective = UserRestrictionsUtils.clone(baseRestrictions);
+        UserRestrictionsUtils.merge(effective, global);
+        UserRestrictionsUtils.merge(effective, local);
+
         return effective;
     }
 
@@ -709,14 +775,13 @@
      */
     @Override
     public Bundle getUserRestrictions(int userId) {
-        Bundle restrictions = getEffectiveUserRestrictions(userId);
-        return restrictions != null ? new Bundle(restrictions) : new Bundle();
+        return UserRestrictionsUtils.clone(getEffectiveUserRestrictions(userId));
     }
 
     @Override
     public void setUserRestriction(String key, boolean value, int userId) {
         checkManageUsersPermission("setUserRestriction");
-        if (!UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS.contains(key)) {
+        if (!UserRestrictionsUtils.isSystemControlled(key)) {
             setUserRestrictionNoCheck(key, value, userId);
         }
     }
@@ -731,8 +796,8 @@
         synchronized (mRestrictionsLock) {
             // Note we can't modify Bundles stored in mBaseUserRestrictions directly, so create
             // a copy.
-            final Bundle newRestrictions = new Bundle();
-            UserRestrictionsUtils.merge(newRestrictions, mBaseUserRestrictions.get(userId));
+            final Bundle newRestrictions = UserRestrictionsUtils.clone(
+                    mBaseUserRestrictions.get(userId));
             newRestrictions.putBoolean(key, value);
 
             updateUserRestrictionsInternalLR(newRestrictions, userId);
@@ -740,75 +805,70 @@
     }
 
     /**
-     * Optionally updating user restrictions, calculate the effective user restrictions by
-     * consulting {@link com.android.server.devicepolicy.DevicePolicyManagerService} and also
-     * apply it to {@link com.android.server.AppOpsService}.
-     * TODO applyUserRestrictionsLocked() should also apply to system settings.
+     * Optionally updating user restrictions, calculate the effective user restrictions and also
+     * propagate to other services and system settings.
      *
-     * @param newRestrictions User restrictions to set.  If null, only the effective restrictions
-     *     will be updated.  Note don't pass an existing Bundle in {@link #mBaseUserRestrictions}
-     *     or {@link #mCachedEffectiveUserRestrictions}; that'll most likely cause a sub
+     * @param newRestrictions User restrictions to set.
+     *      If null, will not update user restrictions and only does the propagation.
      * @param userId target user ID.
      */
     @GuardedBy("mRestrictionsLock")
     private void updateUserRestrictionsInternalLR(
             @Nullable Bundle newRestrictions, int userId) {
-        if (DBG) {
-            Log.d(LOG_TAG, "updateUserRestrictionsInternalLocked userId=" + userId
-                    + " bundle=" + newRestrictions);
-        }
-        // Update system restrictions.
+
+        final Bundle prevAppliedRestrictions = UserRestrictionsUtils.nonNull(
+                mAppliedUserRestrictions.get(userId));
+
+        // Update base restrictions.
         if (newRestrictions != null) {
             // If newRestrictions == the current one, it's probably a bug.
-            Preconditions.checkState(mBaseUserRestrictions.get(userId) != newRestrictions);
+            final Bundle prevBaseRestrictions = mBaseUserRestrictions.get(userId);
+
+            Preconditions.checkState(prevBaseRestrictions != newRestrictions);
             Preconditions.checkState(mCachedEffectiveUserRestrictions.get(userId)
                     != newRestrictions);
-            mBaseUserRestrictions.put(userId, newRestrictions);
-            scheduleWriteUser(getUserInfoNoChecks(userId));
+
+            if (!UserRestrictionsUtils.areEqual(prevBaseRestrictions, newRestrictions)) {
+                mBaseUserRestrictions.put(userId, newRestrictions);
+                scheduleWriteUser(getUserInfoNoChecks(userId));
+            }
         }
 
         final Bundle effective = computeEffectiveUserRestrictionsLR(userId);
 
         mCachedEffectiveUserRestrictions.put(userId, effective);
 
-        applyUserRestrictionsLR(userId, effective);
-    }
-
-    @GuardedBy("mRestrictionsLock")
-    private void applyUserRestrictionsLR(int userId, Bundle newRestrictions) {
-        if (newRestrictions == null) {
-            newRestrictions = Bundle.EMPTY;
-        }
-
-        Bundle prevRestrictions = mAppliedUserRestrictions.get(userId);
-        if (prevRestrictions == null) {
-            prevRestrictions = Bundle.EMPTY;
-        }
-
+        // Apply the new restrictions.
         if (DBG) {
-            Log.d(LOG_TAG, "applyUserRestrictionsRL userId=" + userId
-                    + " new=" + newRestrictions + " prev=" + prevRestrictions);
+            debug("Applying user restrictions: userId=" + userId
+                    + " new=" + effective + " prev=" + prevAppliedRestrictions);
         }
 
-        final long token = Binder.clearCallingIdentity();
-        try {
-            mAppOpsService.setUserRestrictions(newRestrictions, userId);
-        } catch (RemoteException e) {
-            Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
-        } finally {
-            Binder.restoreCallingIdentity(token);
+        if (mAppOpsService != null) { // We skip it until system-ready.
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mAppOpsService.setUserRestrictions(effective, userId);
+            } catch (RemoteException e) {
+                Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
-        UserRestrictionsUtils.applyUserRestrictionsLR(
-                mContext, userId, newRestrictions, prevRestrictions);
+        propagateUserRestrictionsLR(userId, effective, prevAppliedRestrictions);
 
-        notifyUserRestrictionsListeners(userId, newRestrictions, prevRestrictions);
-
-        mAppliedUserRestrictions.put(userId, new Bundle(newRestrictions));
+        mAppliedUserRestrictions.put(userId, new Bundle(effective));
     }
 
-    private void notifyUserRestrictionsListeners(final int userId,
+    private void propagateUserRestrictionsLR(final int userId,
             Bundle newRestrictions, Bundle prevRestrictions) {
+        // Note this method doesn't touch any state, meaning it doesn't require mRestrictionsLock
+        // actually, but we still need some kind of synchronization otherwise we might end up
+        // calling listeners out-of-order, thus "LR".
+
+        if (UserRestrictionsUtils.areEqual(newRestrictions, prevRestrictions)) {
+            return;
+        }
 
         final Bundle newRestrictionsFinal = new Bundle(newRestrictions);
         final Bundle prevRestrictionsFinal = new Bundle(prevRestrictions);
@@ -816,6 +876,11 @@
         mHandler.post(new Runnable() {
             @Override
             public void run() {
+                synchronized (mRestrictionsLock) {
+                    UserRestrictionsUtils.applyUserRestrictionsLR(
+                            mContext, userId, newRestrictionsFinal, prevRestrictionsFinal);
+                }
+
                 final UserRestrictionsListener[] listeners;
                 synchronized (mUserRestrictionsListeners) {
                     listeners = new UserRestrictionsListener[mUserRestrictionsListeners.size()];
@@ -829,13 +894,17 @@
         });
     }
 
-    @GuardedBy("mRestrictionsLock")
-    private void updateEffectiveUserRestrictionsLR(int userId) {
+    // Package private for the inner class.
+    void applyUserRestrictionsLR(int userId) {
         updateUserRestrictionsInternalLR(null, userId);
     }
 
     @GuardedBy("mRestrictionsLock")
-    private void updateEffectiveUserRestrictionsForAllUsersLR() {
+    // Package private for the inner class.
+    void applyUserRestrictionsForAllUsersLR() {
+        if (DBG) {
+            debug("applyUserRestrictionsForAllUsersLR");
+        }
         // First, invalidate all cached values.
         mCachedEffectiveUserRestrictions.clear();
 
@@ -856,10 +925,9 @@
                 // It's okay if a new user has started after the getRunningUserIds() call,
                 // because we'll do the same thing (re-calculate the restrictions and apply)
                 // when we start a user.
-                // TODO: "Apply restrictions upon user start hasn't been implemented.  Implement it.
                 synchronized (mRestrictionsLock) {
                     for (int i = 0; i < runningUsers.length; i++) {
-                        updateUserRestrictionsInternalLR(null, runningUsers[i]);
+                        applyUserRestrictionsLR(runningUsers[i]);
                     }
                 }
             }
@@ -1020,6 +1088,8 @@
                 }
             }
 
+            final Bundle newDevicePolicyGlobalUserRestrictions = new Bundle();
+
             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
                 if (type == XmlPullParser.START_TAG) {
                     final String name = parser.getName();
@@ -1044,6 +1114,10 @@
                                         UserRestrictionsUtils
                                                 .readRestrictions(parser, mGuestRestrictions);
                                     }
+                                } else if (parser.getName().equals(TAG_DEVICE_POLICY_RESTRICTIONS)
+                                        ) {
+                                    UserRestrictionsUtils.readRestrictions(parser,
+                                            newDevicePolicyGlobalUserRestrictions);
                                 }
                                 break;
                             }
@@ -1051,6 +1125,9 @@
                     }
                 }
             }
+            synchronized (mRestrictionsLock) {
+                mDevicePolicyGlobalUserRestrictions = newDevicePolicyGlobalUserRestrictions;
+            }
             updateUserIds();
             upgradeIfNecessaryLP();
         } catch (IOException | XmlPullParserException e) {
@@ -1064,6 +1141,7 @@
      * Upgrade steps between versions, either for fixing bugs or changing the data format.
      */
     private void upgradeIfNecessaryLP() {
+        final int originalVersion = mUserVersion;
         int userVersion = mUserVersion;
         if (userVersion < 1) {
             // Assign a proper name for the owner, if not initialized correctly before
@@ -1116,7 +1194,10 @@
                     + USER_VERSION);
         } else {
             mUserVersion = userVersion;
-            writeUserListLP();
+
+            if (originalVersion < mUserVersion) {
+                writeUserListLP();
+            }
         }
     }
 
@@ -1150,6 +1231,9 @@
     }
 
     private void scheduleWriteUser(UserInfo userInfo) {
+        if (DBG) {
+            debug("scheduleWriteUser");
+        }
         // No need to wrap it within a lock -- worst case, we'll just post the same message
         // twice.
         if (!mHandler.hasMessages(WRITE_USER_MSG, userInfo)) {
@@ -1166,6 +1250,9 @@
      * </user>
      */
     private void writeUserLP(UserInfo userInfo) {
+        if (DBG) {
+            debug("writeUserLP " + userInfo);
+        }
         FileOutputStream fos = null;
         AtomicFile userFile = new AtomicFile(new File(mUsersDir, userInfo.id + XML_SUFFIX));
         try {
@@ -1205,13 +1292,12 @@
             serializer.startTag(null, TAG_NAME);
             serializer.text(userInfo.name);
             serializer.endTag(null, TAG_NAME);
-            Bundle restrictions;
             synchronized (mRestrictionsLock) {
-                restrictions = mBaseUserRestrictions.get(userInfo.id);
-            }
-            if (restrictions != null) {
-                UserRestrictionsUtils
-                        .writeRestrictions(serializer, restrictions, TAG_RESTRICTIONS);
+                UserRestrictionsUtils.writeRestrictions(serializer,
+                        mBaseUserRestrictions.get(userInfo.id), TAG_RESTRICTIONS);
+                UserRestrictionsUtils.writeRestrictions(serializer,
+                        mDevicePolicyLocalUserRestrictions.get(userInfo.id),
+                        TAG_DEVICE_POLICY_RESTRICTIONS);
             }
             serializer.endTag(null, TAG_USER);
 
@@ -1232,6 +1318,9 @@
      * </users>
      */
     private void writeUserListLP() {
+        if (DBG) {
+            debug("writeUserList");
+        }
         FileOutputStream fos = null;
         AtomicFile userListFile = new AtomicFile(mUserListFile);
         try {
@@ -1254,6 +1343,10 @@
                         .writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS);
             }
             serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
+            synchronized (mRestrictionsLock) {
+                UserRestrictionsUtils.writeRestrictions(serializer,
+                        mDevicePolicyGlobalUserRestrictions, TAG_DEVICE_POLICY_RESTRICTIONS);
+            }
             int[] userIdsToWrite;
             synchronized (mUsersLock) {
                 userIdsToWrite = new int[mUsers.size()];
@@ -1289,7 +1382,8 @@
         int restrictedProfileParentId = UserInfo.NO_PROFILE_GROUP_ID;
         boolean partial = false;
         boolean guestToRemove = false;
-        Bundle restrictions = new Bundle();
+        Bundle baseRestrictions = new Bundle();
+        Bundle localRestrictions = new Bundle();
 
         FileInputStream fis = null;
         try {
@@ -1346,7 +1440,9 @@
                             name = parser.getText();
                         }
                     } else if (TAG_RESTRICTIONS.equals(tag)) {
-                        UserRestrictionsUtils.readRestrictions(parser, restrictions);
+                        UserRestrictionsUtils.readRestrictions(parser, baseRestrictions);
+                    } else if (TAG_DEVICE_POLICY_RESTRICTIONS.equals(tag)) {
+                        UserRestrictionsUtils.readRestrictions(parser, localRestrictions);
                     }
                 }
             }
@@ -1360,7 +1456,8 @@
             userInfo.profileGroupId = profileGroupId;
             userInfo.restrictedProfileParentId = restrictedProfileParentId;
             synchronized (mRestrictionsLock) {
-                mBaseUserRestrictions.append(id, restrictions);
+                mBaseUserRestrictions.put(id, baseRestrictions);
+                mDevicePolicyLocalUserRestrictions.put(id, localRestrictions);
             }
             return userInfo;
 
@@ -2118,6 +2215,15 @@
     }
 
     /**
+     * Called right before a user starts.  This will not be called for the system user.
+     */
+    public void onBeforeStartUser(int userId) {
+        synchronized (mRestrictionsLock) {
+            applyUserRestrictionsLR(userId);
+        }
+    }
+
+    /**
      * Make a note of the last started time of a user and do some cleanup.
      * @param userId the user that was just foregrounded
      */
@@ -2313,16 +2419,25 @@
                     synchronized (mRestrictionsLock) {
                         UserRestrictionsUtils.dumpRestrictions(
                                 pw, "      ", mBaseUserRestrictions.get(user.id));
+                        pw.println("    Device policy local restrictions:");
+                        UserRestrictionsUtils.dumpRestrictions(
+                                pw, "      ", mDevicePolicyLocalUserRestrictions.get(user.id));
                         pw.println("    Effective restrictions:");
                         UserRestrictionsUtils.dumpRestrictions(
                                 pw, "      ", mCachedEffectiveUserRestrictions.get(user.id));
                     }
+                    pw.println();
                 }
             }
+            pw.println("  Device policy global restrictions:");
+            synchronized (mRestrictionsLock) {
+                UserRestrictionsUtils
+                        .dumpRestrictions(pw, "    ", mDevicePolicyGlobalUserRestrictions);
+            }
             pw.println();
-            pw.println("Guest restrictions:");
+            pw.println("  Guest restrictions:");
             synchronized (mGuestRestrictions) {
-                UserRestrictionsUtils.dumpRestrictions(pw, "  ", mGuestRestrictions);
+                UserRestrictionsUtils.dumpRestrictions(pw, "    ", mGuestRestrictions);
             }
         }
     }
@@ -2354,22 +2469,11 @@
     }
 
     private class LocalService extends UserManagerInternal {
-
         @Override
-        public Object getUserRestrictionsLock() {
-            return mRestrictionsLock;
-        }
-
-        @Override
-        @GuardedBy("mRestrictionsLock")
-        public void updateEffectiveUserRestrictionsLR(int userId) {
-            UserManagerService.this.updateEffectiveUserRestrictionsLR(userId);
-        }
-
-        @Override
-        @GuardedBy("mRestrictionsLock")
-        public void updateEffectiveUserRestrictionsForAllUsersLR() {
-            UserManagerService.this.updateEffectiveUserRestrictionsForAllUsersLR();
+        public void setDevicePolicyUserRestrictions(int userId, @NonNull Bundle localRestrictions,
+                @Nullable Bundle globalRestrictions) {
+            UserManagerService.this.setDevicePolicyUserRestrictions(userId, localRestrictions,
+                    globalRestrictions);
         }
 
         @Override
@@ -2434,4 +2538,9 @@
             pw.println("    Prints all users on the system.");
         }
     }
+
+    private static void debug(String message) {
+        Log.d(LOG_TAG, message +
+                (DBG_WITH_STACKTRACE ? " called at\n" + Debug.getCallers(10, "  ") : ""));
+    }
 }
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 56e8b3e..129cbd3 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -18,6 +18,10 @@
 
 import com.google.android.collect.Sets;
 
+import com.android.internal.util.Preconditions;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.net.Uri;
@@ -26,6 +30,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.Log;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlSerializer;
@@ -79,22 +84,64 @@
             UserManager.DISALLOW_SAFE_BOOT,
             UserManager.ALLOW_PARENT_PROFILE_APP_LINKING,
             UserManager.DISALLOW_RECORD_AUDIO,
+            UserManager.DISALLOW_CAMERA,
     };
 
     /**
      * Set of user restrictions, which can only be enforced by the system.
      */
     public static final Set<String> SYSTEM_CONTROLLED_USER_RESTRICTIONS = Sets.newArraySet(
-            UserManager.DISALLOW_RECORD_AUDIO);
+            UserManager.DISALLOW_RECORD_AUDIO
+    );
 
     /**
      * Set of user restriction which we don't want to persist.
      */
-    public static final Set<String> NON_PERSIST_USER_RESTRICTIONS = Sets.newArraySet(
-            UserManager.DISALLOW_RECORD_AUDIO);
+    private static final Set<String> NON_PERSIST_USER_RESTRICTIONS = Sets.newArraySet(
+            UserManager.DISALLOW_RECORD_AUDIO
+    );
 
-    public static void writeRestrictions(XmlSerializer serializer, Bundle restrictions,
-            String tag) throws IOException {
+    /**
+     * User restrictions that can not be set by profile owners.
+     */
+    private static final Set<String> DEVICE_OWNER_ONLY_RESTRICTIONS = Sets.newArraySet(
+            UserManager.DISALLOW_USB_FILE_TRANSFER,
+            UserManager.DISALLOW_CONFIG_TETHERING,
+            UserManager.DISALLOW_NETWORK_RESET,
+            UserManager.DISALLOW_FACTORY_RESET,
+            UserManager.DISALLOW_ADD_USER,
+            UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
+            UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
+            UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
+            UserManager.DISALLOW_SMS,
+            UserManager.DISALLOW_FUN,
+            UserManager.DISALLOW_SAFE_BOOT,
+            UserManager.DISALLOW_CREATE_WINDOWS
+    );
+
+    /**
+     * User restrictions that can't be changed by device owner or profile owner.
+     */
+    private static final Set<String> IMMUTABLE_BY_OWNERS = Sets.newArraySet(
+            UserManager.DISALLOW_RECORD_AUDIO,
+            UserManager.DISALLOW_WALLPAPER
+    );
+
+    /**
+     * Special user restrictions that can be applied to a user as well as to all users globally,
+     * depending on callers.  When device owner sets them, they'll be applied to all users.
+     */
+    private static final Set<String> GLOBAL_RESTRICTIONS = Sets.newArraySet(
+            UserManager.DISALLOW_ADJUST_VOLUME,
+            UserManager.DISALLOW_UNMUTE_MICROPHONE
+    );
+
+    public static void writeRestrictions(@NonNull XmlSerializer serializer,
+            @Nullable Bundle restrictions, @NonNull String tag) throws IOException {
+        if (restrictions == null) {
+            return;
+        }
+
         serializer.startTag(null, tag);
         for (String key : USER_RESTRICTIONS) {
             if (restrictions.getBoolean(key)
@@ -115,7 +162,31 @@
         }
     }
 
-    public static void merge(Bundle dest, Bundle in) {
+    /**
+     * @return {@code in} itself when it's not null, or an empty bundle (which can writable).
+     */
+    public static Bundle nonNull(@Nullable Bundle in) {
+        return in != null ? in : new Bundle();
+    }
+
+    public static boolean isEmpty(@Nullable Bundle in) {
+        return (in == null) || (in.size() == 0);
+    }
+
+    /**
+     * Creates a copy of the {@code in} Bundle.  If {@code in} is null, it'll return an empty
+     * bundle.
+     *
+     * <p>The resulting {@link Bundle} is always writable. (i.e. it won't return
+     * {@link Bundle#EMPTY})
+     */
+    public static @NonNull Bundle clone(@Nullable Bundle in) {
+        return (in != null) ? new Bundle(in) : new Bundle();
+    }
+
+    public static void merge(@NonNull Bundle dest, @Nullable Bundle in) {
+        Preconditions.checkNotNull(dest);
+        Preconditions.checkArgument(dest != in);
         if (in == null) {
             return;
         }
@@ -127,6 +198,77 @@
     }
 
     /**
+     * @return true if a restriction is "system controlled"; i.e. can not be overwritten via
+     * {@link UserManager#setUserRestriction}.
+     */
+    public static boolean isSystemControlled(String restriction) {
+        return SYSTEM_CONTROLLED_USER_RESTRICTIONS.contains(restriction);
+    }
+
+    /**
+     * @return true if a restriction is settable by device owner.
+     */
+    public static boolean canDeviceOwnerChange(String restriction) {
+        return !IMMUTABLE_BY_OWNERS.contains(restriction);
+    }
+
+    /**
+     * @return true if a restriction is settable by profile owner.
+     */
+    public static boolean canProfileOwnerChange(String restriction) {
+        return !(IMMUTABLE_BY_OWNERS.contains(restriction)
+                || DEVICE_OWNER_ONLY_RESTRICTIONS.contains(restriction));
+    }
+
+    /**
+     * Takes restrictions that can be set by device owner, and sort them into what should be applied
+     * globally and what should be applied only on the current user.
+     */
+    public static void sortToGlobalAndLocal(@Nullable Bundle in, @NonNull Bundle global,
+            @NonNull Bundle local) {
+        if (in == null || in.size() == 0) {
+            return;
+        }
+        for (String key : in.keySet()) {
+            if (!in.getBoolean(key)) {
+                continue;
+            }
+            if (DEVICE_OWNER_ONLY_RESTRICTIONS.contains(key) || GLOBAL_RESTRICTIONS.contains(key)) {
+                global.putBoolean(key, true);
+            } else {
+                local.putBoolean(key, true);
+            }
+        }
+    }
+
+    /**
+     * @return true if two Bundles contain the same user restriction.
+     * A null bundle and an empty bundle are considered to be equal.
+     */
+    public static boolean areEqual(@Nullable Bundle a, @Nullable Bundle b) {
+        if (a == b) {
+            return true;
+        }
+        if (isEmpty(a)) {
+            return isEmpty(b);
+        }
+        if (isEmpty(b)) {
+            return false;
+        }
+        for (String key : a.keySet()) {
+            if (a.getBoolean(key) != b.getBoolean(key)) {
+                return false;
+            }
+        }
+        for (String key : b.keySet()) {
+            if (a.getBoolean(key) != b.getBoolean(key)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
      * Takes a new use restriction set and the previous set, and apply the restrictions that have
      * changed.
      *
@@ -145,7 +287,7 @@
             }
         }
     }
-
+    
     /**
      * Apply each user restriction.
      *
@@ -155,6 +297,10 @@
      */
     private static void applyUserRestrictionLR(Context context, int userId, String key,
             boolean newValue) {
+        if (UserManagerService.DBG) {
+            Log.d(TAG, "Applying user restriction: userId=" + userId
+                    + " key=" + key + " value=" + 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
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 31c3670..17fa5e5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -201,31 +201,6 @@
     private static final int STATUS_BAR_DISABLE2_MASK =
             StatusBarManager.DISABLE2_QUICK_SETTINGS;
 
-    private static final Set<String> DEVICE_OWNER_USER_RESTRICTIONS;
-    static {
-        DEVICE_OWNER_USER_RESTRICTIONS = new ArraySet<>();
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_USB_FILE_TRANSFER);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_CONFIG_TETHERING);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_NETWORK_RESET);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_FACTORY_RESET);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_ADD_USER);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_CONFIG_CELL_BROADCASTS);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_SMS);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_FUN);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_SAFE_BOOT);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_CREATE_WINDOWS);
-    }
-
-    // The following user restrictions cannot be changed by any active admin, including device
-    // owner and profile owner.
-    private static final Set<String> IMMUTABLE_USER_RESTRICTIONS;
-    static {
-        IMMUTABLE_USER_RESTRICTIONS = new ArraySet<>();
-        IMMUTABLE_USER_RESTRICTIONS.add(UserManager.DISALLOW_WALLPAPER);
-    }
-
     private static final Set<String> SECURE_SETTINGS_WHITELIST;
     private static final Set<String> SECURE_SETTINGS_DEVICEOWNER_WHITELIST;
     private static final Set<String> GLOBAL_SETTINGS_WHITELIST;
@@ -306,6 +281,11 @@
         public void onBootPhase(int phase) {
             mService.systemReady(phase);
         }
+
+        @Override
+        public void onStartUser(int userHandle) {
+            mService.onStartUser(userHandle);
+        }
     }
 
     public static class DevicePolicyData {
@@ -1014,7 +994,6 @@
         }
     }
 
-    // DO NOT call it while taking the "this" lock, which could cause a dead lock.
     private void handlePackagesChanged(String packageName, int userHandle) {
         boolean removed = false;
         if (VERBOSE_LOG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle);
@@ -1060,11 +1039,8 @@
             }
         }
         if (removed) {
-            synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
-                synchronized (DevicePolicyManagerService.this) {
-                    mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
-                }
-            }
+            // The removed admin might have disabled camera, so update user restrictions.
+            pushUserRestrictions(userHandle);
         }
     }
 
@@ -1350,8 +1326,6 @@
             // TODO PO may not have a class name either due to b/17652534.  Address that too.
 
             updateDeviceOwnerLocked();
-
-            // TODO Notify UM to update restrictions (?)
         }
     }
 
@@ -1403,6 +1377,9 @@
             migrateUserRestrictionsForUser(UserHandle.SYSTEM, deviceOwnerAdmin,
                     /* exceptionList =*/ UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS);
 
+            // Push DO user restrictions to user manager.
+            pushUserRestrictions(UserHandle.USER_SYSTEM);
+
             mOwners.setDeviceOwnerUserRestrictionsMigrated();
         }
 
@@ -1432,6 +1409,13 @@
 
                     migrateUserRestrictionsForUser(ui.getUserHandle(), profileOwnerAdmin,
                             exceptionList);
+
+                    // Note if a secondary user has no PO but has a DA that disables camera, we
+                    // don't get here and won't push the camera user restriction to UserManager
+                    // here.  That's okay because we'll push user restrictions anyway when a user
+                    // starts.  But we still do it because we want to let user manager persist
+                    // upon migration.
+                    pushUserRestrictions(userId);
                 }
 
                 mOwners.setProfileOwnerUserRestrictionsMigrated(userId);
@@ -1709,12 +1693,9 @@
                                 updateMaximumTimeToLockLocked(policy);
                                 policy.mRemovingAdmins.remove(adminReceiver);
                             }
-                            synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
-                                synchronized (DevicePolicyManagerService.this) {
-                                    mUserManagerInternal.updateEffectiveUserRestrictionsLR(
-                                            userHandle);
-                                }
-                            }
+                            // The removed admin might have disabled camera, so update user
+                            // restrictions.
+                            pushUserRestrictions(userHandle);
                         }
                     });
         }
@@ -2113,19 +2094,13 @@
         getUserData(UserHandle.USER_SYSTEM);
         loadOwners();
         cleanUpOldUsers();
+
+        onStartUser(UserHandle.USER_SYSTEM);
+
         // Register an observer for watching for user setup complete.
         new SetupContentObserver(mHandler).register(mContext.getContentResolver());
         // Initialize the user setup state, to handle the upgrade case.
         updateUserSetupComplete();
-
-        // Update the screen capture disabled cache in the window manager
-        List<UserInfo> users = mUserManager.getUsers(true);
-        final int N = users.size();
-        for (int i = 0; i < N; i++) {
-            int userHandle = users.get(i).id;
-            updateScreenCaptureDisabledInWindowManager(userHandle,
-                    getScreenCaptureDisabled(null, userHandle));
-        }
     }
 
     private void ensureDeviceOwnerUserStarted() {
@@ -2147,6 +2122,12 @@
         }
     }
 
+    private void onStartUser(int userId) {
+        updateScreenCaptureDisabledInWindowManager(userId,
+                getScreenCaptureDisabled(null, userId));
+        pushUserRestrictions(userId);
+    }
+
     private void cleanUpOldUsers() {
         // This is needed in case the broadcast {@link Intent.ACTION_USER_REMOVED} was not handled
         // before reboot
@@ -4307,15 +4288,18 @@
         }
     }
 
-    private void updateScreenCaptureDisabledInWindowManager(int userHandle, boolean disabled) {
-        long ident = mInjector.binderClearCallingIdentity();
-        try {
-            mInjector.getIWindowManager().setScreenCaptureDisabled(userHandle, disabled);
-        } catch (RemoteException e) {
-            Log.w(LOG_TAG, "Unable to notify WindowManager.", e);
-        } finally {
-            mInjector.binderRestoreCallingIdentity(ident);
-        }
+    private void updateScreenCaptureDisabledInWindowManager(final int userHandle,
+            final boolean disabled) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    mInjector.getIWindowManager().setScreenCaptureDisabled(userHandle, disabled);
+                } catch (RemoteException e) {
+                    Log.w(LOG_TAG, "Unable to notify WindowManager.", e);
+                }
+            }
+        });
     }
 
     /**
@@ -4381,15 +4365,7 @@
             }
         }
         // Tell the user manager that the restrictions have changed.
-        synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
-            synchronized (this) {
-                if (isDeviceOwner(who, userHandle)) {
-                    mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersLR();
-                } else {
-                    mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
-                }
-            }
-        }
+        pushUserRestrictions(userHandle);
     }
 
     /**
@@ -4398,6 +4374,11 @@
      */
     @Override
     public boolean getCameraDisabled(ComponentName who, int userHandle) {
+        return getCameraDisabled(who, userHandle, /* mergeDeviceOwnerRestriction= */ true);
+    }
+
+    private boolean getCameraDisabled(ComponentName who, int userHandle,
+            boolean mergeDeviceOwnerRestriction) {
         if (!mHasFeature) {
             return false;
         }
@@ -4407,9 +4388,11 @@
                 return (admin != null) ? admin.disableCamera : false;
             }
             // First, see if DO has set it.  If so, it's device-wide.
-            final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
-            if (deviceOwner != null && deviceOwner.disableCamera) {
-                return true;
+            if (mergeDeviceOwnerRestriction) {
+                final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+                if (deviceOwner != null && deviceOwner.disableCamera) {
+                    return true;
+                }
             }
 
             // Then check each device admin on the user.
@@ -4602,6 +4585,7 @@
                 return admin;
             }
         }
+        Slog.wtf(LOG_TAG, "Active admin for device owner not found. component=" + component);
         return null;
     }
 
@@ -4622,6 +4606,11 @@
             throw new SecurityException("clearDeviceOwner can only be called by the device owner");
         }
         synchronized (this) {
+            final ActiveAdmin admin = getDeviceOwnerAdminLocked();
+            if (admin != null) {
+                admin.disableCamera = false;
+                admin.userRestrictions = null;
+            }
             clearUserPoliciesLocked(new UserHandle(UserHandle.USER_SYSTEM));
 
             mOwners.clearDeviceOwner();
@@ -4667,6 +4656,7 @@
         final ActiveAdmin admin =
                 getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
         synchronized (this) {
+            admin.disableCamera = false;
             admin.userRestrictions = null;
             clearUserPoliciesLocked(callingUser);
             final int userId = callingUser.getIdentifier();
@@ -4713,9 +4703,7 @@
             mIPackageManager.updatePermissionFlagsForAllApps(
                     PackageManager.FLAG_PERMISSION_POLICY_FIXED,
                     0  /* flagValues */, userHandle.getIdentifier());
-            synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
-                mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle.getIdentifier());
-            }
+            pushUserRestrictions(userHandle.getIdentifier());
         } catch (RemoteException re) {
         } finally {
             mInjector.binderRestoreCallingIdentity(ident);
@@ -5004,31 +4992,30 @@
             return;
         }
 
-        final Printer p = new PrintWriterPrinter(pw);
-
         synchronized (this) {
-            p.println("Current Device Policy Manager state:");
+            pw.println("Current Device Policy Manager state:");
             mOwners.dump("  ", pw);
             int userCount = mUserData.size();
             for (int u = 0; u < userCount; u++) {
                 DevicePolicyData policy = getUserData(mUserData.keyAt(u));
-                p.println("  Enabled Device Admins (User " + policy.mUserHandle + "):");
+                pw.println();
+                pw.println("  Enabled Device Admins (User " + policy.mUserHandle + "):");
                 final int N = policy.mAdminList.size();
                 for (int i=0; i<N; i++) {
                     ActiveAdmin ap = policy.mAdminList.get(i);
                     if (ap != null) {
-                        pw.print("  "); pw.print(ap.info.getComponent().flattenToShortString());
+                        pw.print("    "); pw.print(ap.info.getComponent().flattenToShortString());
                                 pw.println(":");
-                        ap.dump("    ", pw);
+                        ap.dump("      ", pw);
                     }
                 }
                 if (!policy.mRemovingAdmins.isEmpty()) {
-                    p.println("  Removing Device Admins (User " + policy.mUserHandle + "): "
+                    pw.println("    Removing Device Admins (User " + policy.mUserHandle + "): "
                             + policy.mRemovingAdmins);
                 }
 
                 pw.println(" ");
-                pw.print("  mPasswordOwner="); pw.println(policy.mPasswordOwner);
+                pw.print("    mPasswordOwner="); pw.println(policy.mPasswordOwner);
             }
         }
     }
@@ -5666,47 +5653,67 @@
         }
     }
 
-    // DO NOT call it while taking the "this" lock, which could cause a dead lock.
     @Override
     public void setUserRestriction(ComponentName who, String key, boolean enabledFromThisOwner) {
         Preconditions.checkNotNull(who, "ComponentName is null");
         final int userHandle = mInjector.userHandleGetCallingUserId();
-        synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
-            synchronized (this) {
-                ActiveAdmin activeAdmin =
-                        getActiveAdminForCallerLocked(who,
-                                DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-                final boolean isDeviceOwner = isDeviceOwner(who, userHandle);
-                if (!isDeviceOwner && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) {
-                    throw new SecurityException(
-                            "Profile owners cannot set user restriction " + key);
+        synchronized (this) {
+            ActiveAdmin activeAdmin =
+                    getActiveAdminForCallerLocked(who,
+                            DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            final boolean isDeviceOwner = isDeviceOwner(who, userHandle);
+            if (isDeviceOwner) {
+                if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
+                    throw new SecurityException("Device owner cannot set user restriction " + key);
                 }
-                if (IMMUTABLE_USER_RESTRICTIONS.contains(key)) {
-                    throw new SecurityException("User restriction " + key + " cannot be changed");
+            } else { // profile owner
+                if (!UserRestrictionsUtils.canProfileOwnerChange(key)) {
+                    throw new SecurityException("Profile owner cannot set user restriction " + key);
                 }
-
-                final long id = mInjector.binderClearCallingIdentity();
-                try {
-                    // 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,
-                    // and need to have another Bundle in DO active admin to hold restrictions as
-                    // PO.
-                    activeAdmin.ensureUserRestrictions().putBoolean(key, enabledFromThisOwner);
-                    saveSettingsLocked(userHandle);
-
-                    // Tell UserManager the new value.
-                    if (isDeviceOwner) {
-                        mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersLR();
-                    } else {
-                        mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
-                    }
-                } finally {
-                    mInjector.binderRestoreCallingIdentity(id);
-                }
-
-                sendChangedNotification(userHandle);
             }
+
+            // Save the restriction to ActiveAdmin.
+            activeAdmin.ensureUserRestrictions().putBoolean(key, enabledFromThisOwner);
+            saveSettingsLocked(userHandle);
+
+            pushUserRestrictions(userHandle);
+
+            sendChangedNotification(userHandle);
+        }
+    }
+
+    private void pushUserRestrictions(int userId) {
+        synchronized (this) {
+            final Bundle global;
+            final Bundle local = new Bundle();
+            if (mOwners.isDeviceOwnerUserId(userId)) {
+                global = new Bundle();
+
+                final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+                if (deviceOwner == null) {
+                    return; // Shouldn't happen.
+                }
+
+                UserRestrictionsUtils.sortToGlobalAndLocal(deviceOwner.userRestrictions,
+                        global, local);
+                // DO can disable camera globally.
+                if (deviceOwner.disableCamera) {
+                    global.putBoolean(UserManager.DISALLOW_CAMERA, true);
+                }
+            } else {
+                global = null;
+
+                ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId);
+                if (profileOwner != null) {
+                    UserRestrictionsUtils.merge(local, profileOwner.userRestrictions);
+                }
+            }
+            // Also merge in *local* camera restriction.
+            if (getCameraDisabled(/* who= */ null,
+                    userId, /* mergeDeviceOwnerRestriction= */ false)) {
+                local.putBoolean(UserManager.DISALLOW_CAMERA, true);
+            }
+            mUserManagerInternal.setDevicePolicyUserRestrictions(userId, local, global);
         }
     }
 
@@ -6448,37 +6455,6 @@
             }
         }
 
-        @Override
-        public Bundle getComposedUserRestrictions(int userId, Bundle inBundle) {
-            synchronized (DevicePolicyManagerService.this) {
-                final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
-                final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId);
-
-                final Bundle deviceOwnerRestrictions =
-                        deviceOwner == null ? null : deviceOwner.userRestrictions;
-                final Bundle profileOwnerRestrictions =
-                        profileOwner == null ? null : profileOwner.userRestrictions;
-                final boolean cameraDisabled = getCameraDisabled(null, userId);
-
-                if (deviceOwnerRestrictions == null && profileOwnerRestrictions == null
-                        && !cameraDisabled) {
-                    // No restrictions to merge.
-                    return inBundle;
-                }
-
-                final Bundle composed = new Bundle(inBundle);
-                UserRestrictionsUtils.merge(composed, deviceOwnerRestrictions);
-                UserRestrictionsUtils.merge(composed, profileOwnerRestrictions);
-
-                // Also merge in the camera restriction.
-                if (cameraDisabled) {
-                    composed.putBoolean(UserManager.DISALLOW_CAMERA, true);
-                }
-
-                return composed;
-            }
-        }
-
         private void notifyCrossProfileProvidersChanged(int userId, List<String> packages) {
             final List<OnCrossProfileWidgetProvidersChangeListener> listeners;
             synchronized (DevicePolicyManagerService.this) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index ded4422..435de7a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -221,6 +221,10 @@
         return mDeviceOwner != null;
     }
 
+    boolean isDeviceOwnerUserId(int userId) {
+        return mDeviceOwner != null && mDeviceOwnerUserId == userId;
+    }
+
     boolean hasProfileOwner(int userId) {
         return getProfileOwnerComponent(userId) != null;
     }
@@ -625,20 +629,30 @@
     }
 
     public void dump(String prefix, PrintWriter pw) {
+        boolean needBlank = false;
         if (mDeviceOwner != null) {
             pw.println(prefix + "Device Owner: ");
             mDeviceOwner.dump(prefix + "  ", pw);
             pw.println(prefix + "  User ID: " + mDeviceOwnerUserId);
-            pw.println();
+            needBlank = true;
         }
         if (mSystemUpdatePolicy != null) {
+            if (needBlank) {
+                needBlank = false;
+                pw.println();
+            }
             pw.println(prefix + "System Update Policy: " + mSystemUpdatePolicy);
-            pw.println();
+            needBlank = true;
         }
         if (mProfileOwners != null) {
             for (Map.Entry<Integer, OwnerInfo> entry : mProfileOwners.entrySet()) {
+                if (needBlank) {
+                    needBlank = false;
+                    pw.println();
+                }
                 pw.println(prefix + "Profile Owner (User " + entry.getKey() + "): ");
                 entry.getValue().dump(prefix + "  ", pw);
+                needBlank = true;
             }
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 36980e3..0bd4896 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -25,12 +25,8 @@
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.os.Bundle;
-import android.content.pm.PackageInfo;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Pair;
@@ -50,6 +46,7 @@
 import static org.mockito.Matchers.isNull;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -768,21 +765,42 @@
                 dpm.getUserRestrictions(admin1)
         );
 
-        dpm.addUserRestriction(admin1, UserManager.DISALLOW_SMS);
+        reset(mContext.userManagerInternal);
+
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADD_USER);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                MockUtils.checkUserRestrictions(),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER)
+                );
+        reset(mContext.userManagerInternal);
+
         dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER)
+        );
+        reset(mContext.userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(
-                        UserManager.DISALLOW_SMS, UserManager.DISALLOW_OUTGOING_CALLS),
+                        UserManager.DISALLOW_ADD_USER, UserManager.DISALLOW_OUTGOING_CALLS),
                 dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
         );
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(
-                        UserManager.DISALLOW_SMS, UserManager.DISALLOW_OUTGOING_CALLS),
+                        UserManager.DISALLOW_ADD_USER, UserManager.DISALLOW_OUTGOING_CALLS),
                 dpm.getUserRestrictions(admin1)
         );
 
-        dpm.clearUserRestriction(admin1, UserManager.DISALLOW_SMS);
+        dpm.clearUserRestriction(admin1, UserManager.DISALLOW_ADD_USER);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
+                MockUtils.checkUserRestrictions()
+        );
+        reset(mContext.userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
@@ -794,6 +812,12 @@
         );
 
         dpm.clearUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                MockUtils.checkUserRestrictions(),
+                MockUtils.checkUserRestrictions()
+        );
+        reset(mContext.userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(),
@@ -804,7 +828,68 @@
                 dpm.getUserRestrictions(admin1)
         );
 
-        // TODO Check inner calls.
+        // DISALLOW_ADJUST_VOLUME and DISALLOW_UNMUTE_MICROPHONE are PO restrictions, but when
+        // DO sets them, the scope is global.
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADJUST_VOLUME);
+        reset(mContext.userManagerInternal);
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_UNMUTE_MICROPHONE);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                MockUtils.checkUserRestrictions(),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADJUST_VOLUME,
+                        UserManager.DISALLOW_UNMUTE_MICROPHONE)
+        );
+        reset(mContext.userManagerInternal);
+
+        dpm.clearUserRestriction(admin1, UserManager.DISALLOW_ADJUST_VOLUME);
+        dpm.clearUserRestriction(admin1, UserManager.DISALLOW_UNMUTE_MICROPHONE);
+
+
+        // More tests.
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADD_USER);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                MockUtils.checkUserRestrictions(),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER)
+        );
+        reset(mContext.userManagerInternal);
+
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_FUN);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                MockUtils.checkUserRestrictions(),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN,
+                        UserManager.DISALLOW_ADD_USER)
+        );
+        reset(mContext.userManagerInternal);
+
+        dpm.setCameraDisabled(admin1, true);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                // DISALLOW_CAMERA will be applied to both local and global.
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_CAMERA),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN,
+                        UserManager.DISALLOW_CAMERA, UserManager.DISALLOW_ADD_USER)
+        );
+        reset(mContext.userManagerInternal);
+
+        // Set up another DA and let it disable camera.  Now DISALLOW_CAMERA will only be applied
+        // locally.
+        dpm.setCameraDisabled(admin1, false);
+        reset(mContext.userManagerInternal);
+
+        setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_SYSTEM_USER_UID);
+        dpm.setActiveAdmin(admin2, /* replace =*/ false, UserHandle.USER_SYSTEM);
+        dpm.setCameraDisabled(admin2, true);
+
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                // DISALLOW_CAMERA will be applied to both local and global.
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_CAMERA),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN,
+                        UserManager.DISALLOW_ADD_USER)
+        );
+        reset(mContext.userManagerInternal);
         // TODO Make sure restrictions are written to the file.
     }
 
@@ -818,7 +903,21 @@
         );
 
         dpm.addUserRestriction(admin1, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(DpmMockContext.CALLER_USER_HANDLE),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES),
+                isNull(Bundle.class)
+        );
+        reset(mContext.userManagerInternal);
+
         dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(DpmMockContext.CALLER_USER_HANDLE),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+                        UserManager.DISALLOW_OUTGOING_CALLS),
+                isNull(Bundle.class)
+        );
+        reset(mContext.userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(
@@ -837,7 +936,12 @@
         );
 
         dpm.clearUserRestriction(admin1, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
-
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(DpmMockContext.CALLER_USER_HANDLE),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
+                isNull(Bundle.class)
+        );
+        reset(mContext.userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(
@@ -854,6 +958,12 @@
         );
 
         dpm.clearUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(DpmMockContext.CALLER_USER_HANDLE),
+                MockUtils.checkUserRestrictions(),
+                isNull(Bundle.class)
+        );
+        reset(mContext.userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(),
@@ -865,69 +975,29 @@
                 dpm.getUserRestrictions(admin1)
         );
 
-        // TODO Check inner calls.
+        // DISALLOW_ADJUST_VOLUME and DISALLOW_UNMUTE_MICROPHONE can be set by PO too, even
+        // though when DO sets them they'll be applied globally.
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADJUST_VOLUME);
+        reset(mContext.userManagerInternal);
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_UNMUTE_MICROPHONE);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(DpmMockContext.CALLER_USER_HANDLE),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADJUST_VOLUME,
+                        UserManager.DISALLOW_UNMUTE_MICROPHONE),
+                isNull(Bundle.class)
+        );
+        reset(mContext.userManagerInternal);
+
+        dpm.setCameraDisabled(admin1, true);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(DpmMockContext.CALLER_USER_HANDLE),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_CAMERA,
+                        UserManager.DISALLOW_ADJUST_VOLUME,
+                        UserManager.DISALLOW_UNMUTE_MICROPHONE),
+                isNull(Bundle.class)
+        );
+        reset(mContext.userManagerInternal);
+
         // TODO Make sure restrictions are written to the file.
     }
-
-    public void testGetComposedUserRestrictions_noDoNoPo() throws Exception {
-        final Bundle in = DpmTestUtils.newRestrictions(UserManager.DISALLOW_OUTGOING_CALLS);
-
-        Bundle actual = dpms.mLocalService.getComposedUserRestrictions(
-                UserHandle.USER_SYSTEM, in);
-        assertTrue(in == actual);
-
-        actual = dpms.mLocalService.getComposedUserRestrictions(
-                DpmMockContext.CALLER_USER_HANDLE, in);
-        assertTrue(in == actual);
-    }
-
-    public void testGetComposedUserRestrictions() throws Exception {
-        mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
-        mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
-        mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL);
-
-        // First, set DO.
-
-        // Call from a process on the system user.
-        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
-
-        // Make sure admin1 is installed on system user.
-        setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
-
-        // Call.
-        dpm.setActiveAdmin(admin1, /* replace =*/ false, UserHandle.USER_SYSTEM);
-        assertTrue(dpm.setDeviceOwner(admin1, "owner-name",
-                UserHandle.USER_SYSTEM));
-
-        dpm.addUserRestriction(admin1, "rest1");
-        dpm.addUserRestriction(admin1, "rest2");
-
-        // Set PO on CALLER_USER_HANDLE.
-        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
-
-        setAsProfileOwner(admin2);
-
-        dpm.addUserRestriction(admin2, "restA");
-        dpm.addUserRestriction(admin2, "restB");
-
-        final Bundle in = DpmTestUtils.newRestrictions("abc");
-
-        Bundle actual = dpms.mLocalService.getComposedUserRestrictions(
-                UserHandle.USER_SYSTEM, in);
-        DpmTestUtils.assertRestrictions(
-                DpmTestUtils.newRestrictions("abc", "rest1", "rest2"),
-                actual);
-
-        actual = dpms.mLocalService.getComposedUserRestrictions(
-                DpmMockContext.CALLER_USER_HANDLE, in);
-        DpmTestUtils.assertRestrictions(
-                DpmTestUtils.newRestrictions("abc", "rest1", "rest2", "restA", "restB"),
-                actual);
-
-        actual = dpms.mLocalService.getComposedUserRestrictions(
-                DpmMockContext.CALLER_USER_HANDLE + 1, in);
-        DpmTestUtils.assertRestrictions(
-                DpmTestUtils.newRestrictions("abc", "rest1", "rest2"),
-                actual);
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index cc337b0..f4fdc95 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -260,9 +260,6 @@
 
         // System user is always running.
         setUserRunning(UserHandle.USER_SYSTEM, true);
-
-        // This method must return an object.
-        when(userManagerInternal.getUserRestrictionsLock()).thenReturn(new Object());
     }
 
     public File addUser(int userId, int flags) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
index 5008fbf..58db192 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
@@ -17,8 +17,12 @@
 
 import com.google.common.base.Objects;
 
+import com.android.internal.util.Preconditions;
+import com.android.server.pm.UserRestrictionsUtils;
+
 import android.content.ComponentName;
 import android.content.Intent;
+import android.os.Bundle;
 import android.os.UserHandle;
 
 import org.hamcrest.BaseMatcher;
@@ -77,4 +81,37 @@
         };
         return Mockito.argThat(m);
     }
+
+    public static Bundle checkUserRestrictions(String... keys) {
+        final Bundle expected = DpmTestUtils.newRestrictions(Preconditions.checkNotNull(keys));
+        final Matcher<Bundle> m = new BaseMatcher<Bundle>() {
+            @Override
+            public boolean matches(Object item) {
+                if (item == null) return false;
+                return UserRestrictionsUtils.areEqual((Bundle) item, expected);
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("User restrictions=" + getRestrictionsAsString(expected));
+            }
+        };
+        return Mockito.argThat(m);
+    }
+
+    private static String getRestrictionsAsString(Bundle b) {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("[");
+
+        if (b != null) {
+            String sep = "";
+            for (String key : b.keySet()) {
+                sb.append(sep);
+                sep = ",";
+                sb.append(key);
+            }
+        }
+        sb.append("]");
+        return sb.toString();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
index 4e11762..423c4d5 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -19,21 +19,7 @@
 import com.android.server.devicepolicy.DevicePolicyManagerServiceTestable.OwnersTestable;
 
 import android.content.ComponentName;
-import android.content.pm.UserInfo;
 import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-
-import static org.mockito.Mockito.when;
 
 /**
  * Tests for the DeviceOwner object that saves & loads device and policy owner information.
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
new file mode 100644
index 0000000..56fd351
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2015 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.server.pm;
+
+import com.android.server.devicepolicy.DpmTestUtils;
+
+import android.os.Bundle;
+import android.os.UserManager;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+
+/**
+ * Tests for {@link com.android.server.pm.UserRestrictionsUtils}.
+ *
+ * <p>Run with:<pre>
+   m FrameworksServicesTests &&
+   adb install \
+     -r out/target/product/hammerhead/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
+   adb shell am instrument -e class com.android.server.pm.UserRestrictionsUtilsTest \
+     -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ * </pre>
+ */
+public class UserRestrictionsUtilsTest extends AndroidTestCase {
+    public void testNonNull() {
+        Bundle out = UserRestrictionsUtils.nonNull(null);
+        assertNotNull(out);
+        out.putBoolean("a", true); // Should not be Bundle.EMPTY.
+
+        Bundle in = new Bundle();
+        assertSame(in, UserRestrictionsUtils.nonNull(in));
+    }
+
+    public void testIsEmpty() {
+        assertTrue(UserRestrictionsUtils.isEmpty(null));
+        assertTrue(UserRestrictionsUtils.isEmpty(new Bundle()));
+        assertFalse(UserRestrictionsUtils.isEmpty(DpmTestUtils.newRestrictions("a")));
+    }
+
+    public void testClone() {
+        Bundle in = new Bundle();
+        Bundle out = UserRestrictionsUtils.clone(in);
+        assertNotSame(in, out);
+        DpmTestUtils.assertRestrictions(out, new Bundle());
+
+        out = UserRestrictionsUtils.clone(null);
+        assertNotNull(out);
+        out.putBoolean("a", true); // Should not be Bundle.EMPTY.
+    }
+
+    public void testMerge() {
+        Bundle a = DpmTestUtils.newRestrictions("a", "d");
+        Bundle b = DpmTestUtils.newRestrictions("b", "d", "e");
+
+        UserRestrictionsUtils.merge(a, b);
+
+        DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions("a", "b", "d", "e"), a);
+
+        UserRestrictionsUtils.merge(a, null);
+
+        DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions("a", "b", "d", "e"), a);
+
+        try {
+            UserRestrictionsUtils.merge(a, a);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testIsSystemControlled() {
+        assertTrue(UserRestrictionsUtils.isSystemControlled(UserManager.DISALLOW_RECORD_AUDIO));
+        assertFalse(UserRestrictionsUtils.isSystemControlled(UserManager.DISALLOW_FUN));
+    }
+
+    public void testCanDeviceOwnerChange() {
+        assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_RECORD_AUDIO));
+        assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_WALLPAPER));
+        assertTrue(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_ADD_USER));
+    }
+
+    public void testCanProfileOwnerChange() {
+        assertFalse(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_RECORD_AUDIO));
+        assertFalse(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_WALLPAPER));
+        assertFalse(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_ADD_USER));
+        assertTrue(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_ADJUST_VOLUME));
+    }
+
+    public void testSortToGlobalAndLocal() {
+        final Bundle local = new Bundle();
+        final Bundle global = new Bundle();
+
+        UserRestrictionsUtils.sortToGlobalAndLocal(null, global, local);
+        assertEquals(0, global.size());
+        assertEquals(0, local.size());
+
+        UserRestrictionsUtils.sortToGlobalAndLocal(Bundle.EMPTY, global, local);
+        assertEquals(0, global.size());
+        assertEquals(0, local.size());
+
+        UserRestrictionsUtils.sortToGlobalAndLocal(DpmTestUtils.newRestrictions(
+                UserManager.DISALLOW_ADJUST_VOLUME,
+                UserManager.DISALLOW_UNMUTE_MICROPHONE,
+                UserManager.DISALLOW_USB_FILE_TRANSFER,
+                UserManager.DISALLOW_CONFIG_TETHERING,
+                UserManager.DISALLOW_OUTGOING_BEAM,
+                UserManager.DISALLOW_APPS_CONTROL
+        ), global, local);
+
+
+        DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions(
+                // These can be set by PO too, but when DO sets them, they're global.
+                UserManager.DISALLOW_ADJUST_VOLUME,
+                UserManager.DISALLOW_UNMUTE_MICROPHONE,
+
+                // These can only be set by DO.
+                UserManager.DISALLOW_USB_FILE_TRANSFER,
+                UserManager.DISALLOW_CONFIG_TETHERING
+        ), global);
+
+        DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions(
+                // They can be set by both DO/PO.
+                UserManager.DISALLOW_OUTGOING_BEAM,
+                UserManager.DISALLOW_APPS_CONTROL
+        ), local);
+    }
+
+    public void testAreEqual() {
+        assertTrue(UserRestrictionsUtils.areEqual(
+                null,
+                null));
+
+        assertTrue(UserRestrictionsUtils.areEqual(
+                null,
+                Bundle.EMPTY));
+
+        assertTrue(UserRestrictionsUtils.areEqual(
+                Bundle.EMPTY,
+                null));
+
+        assertTrue(UserRestrictionsUtils.areEqual(
+                Bundle.EMPTY,
+                Bundle.EMPTY));
+
+        assertTrue(UserRestrictionsUtils.areEqual(
+                new Bundle(),
+                Bundle.EMPTY));
+
+        assertFalse(UserRestrictionsUtils.areEqual(
+                null,
+                DpmTestUtils.newRestrictions("a")));
+
+        assertFalse(UserRestrictionsUtils.areEqual(
+                DpmTestUtils.newRestrictions("a"),
+                null));
+
+        assertTrue(UserRestrictionsUtils.areEqual(
+                DpmTestUtils.newRestrictions("a"),
+                DpmTestUtils.newRestrictions("a")));
+
+        assertFalse(UserRestrictionsUtils.areEqual(
+                DpmTestUtils.newRestrictions("a"),
+                DpmTestUtils.newRestrictions("a", "b")));
+
+        assertFalse(UserRestrictionsUtils.areEqual(
+                DpmTestUtils.newRestrictions("a", "b"),
+                DpmTestUtils.newRestrictions("a")));
+
+        assertFalse(UserRestrictionsUtils.areEqual(
+                DpmTestUtils.newRestrictions("b", "a"),
+                DpmTestUtils.newRestrictions("a", "a")));
+
+        // Make sure false restrictions are handled correctly.
+        final Bundle a = DpmTestUtils.newRestrictions("a");
+        a.putBoolean("b", true);
+
+        final Bundle b = DpmTestUtils.newRestrictions("a");
+        b.putBoolean("b", false);
+
+        assertFalse(UserRestrictionsUtils.areEqual(a, b));
+        assertFalse(UserRestrictionsUtils.areEqual(b, a));
+    }
+}