Resolve policies that could be set locally and globally

Also added userControlDisabledPackages policy

Bug: 258442697
Bug: 232918480
Test: atest android.devicepolicy.cts.UserControlDisabledPackagesTest
Change-Id: I4ce87c0351f5b76ac77ba0ec556a9b4dbf6b0ac1
diff --git a/core/api/current.txt b/core/api/current.txt
index 5dd1b39..298147c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8160,6 +8160,7 @@
     field @NonNull public static final android.app.admin.TargetUser GLOBAL;
     field @NonNull public static final android.app.admin.TargetUser LOCAL_USER;
     field @NonNull public static final android.app.admin.TargetUser PARENT_USER;
+    field @NonNull public static final android.app.admin.TargetUser UNKNOWN_USER;
   }
 
   public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 209b112..28a9b51 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3996,6 +3996,12 @@
      */
     public static final String LOCK_TASK_POLICY = "lockTask";
 
+    // TODO: Expose this as SystemAPI once we add the query API
+    /**
+     * @hide
+     */
+    public static final String USER_CONTROL_DISABLED_PACKAGES = "userControlDisabledPackages";
+
     /**
      * This object is a single place to tack on invalidation and disable calls.  All
      * binder caches in this class derive from this Config, so all can be invalidated or
diff --git a/core/java/android/app/admin/TargetUser.java b/core/java/android/app/admin/TargetUser.java
index acbac29..1ec2d52 100644
--- a/core/java/android/app/admin/TargetUser.java
+++ b/core/java/android/app/admin/TargetUser.java
@@ -44,6 +44,11 @@
     public static final int GLOBAL_USER_ID = -3;
 
     /**
+     * @hide
+     */
+    public static final int UNKNOWN_USER_ID = -3;
+
+    /**
      * Indicates that the policy relates to the user the admin is installed on.
      */
     @NonNull
@@ -61,6 +66,15 @@
     @NonNull
     public static final TargetUser GLOBAL = new TargetUser(GLOBAL_USER_ID);
 
+    /**
+     * Indicates that the policy relates to some unknown user on the device. For example, if Admin1
+     * has set a global policy on a device and Admin2 has set a conflicting local
+     * policy on some other secondary user, Admin1 will get a policy update callback with
+     * {@code UNKNOWN_USER} as the target user.
+     */
+    @NonNull
+    public static final TargetUser UNKNOWN_USER = new TargetUser(UNKNOWN_USER_ID);
+
     private final int mUserId;
 
     /**
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index d796ddf..a795c3f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -34,9 +34,12 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.AtomicFile;
 import android.util.Log;
 import android.util.SparseArray;
@@ -68,6 +71,8 @@
     static final String TAG = "DevicePolicyEngine";
 
     private final Context mContext;
+    private final UserManager mUserManager;
+
     // TODO(b/256849338): add more granular locks
     private final Object mLock = new Object();
 
@@ -83,6 +88,7 @@
 
     DevicePolicyEngine(@NonNull Context context) {
         mContext = Objects.requireNonNull(context);
+        mUserManager = mContext.getSystemService(UserManager.class);
         mLocalPolicies = new SparseArray<>();
         mGlobalPolicies = new HashMap<>();
     }
@@ -91,10 +97,8 @@
     /**
      * Set the policy for the provided {@code policyDefinition}
      * (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}.
-     * Returns {@code true} if the enforced policy has been changed.
-     *
      */
-    <V> boolean setLocalPolicy(
+    <V> void setLocalPolicy(
             @NonNull PolicyDefinition<V> policyDefinition,
             @NonNull EnforcingAdmin enforcingAdmin,
             @NonNull V value,
@@ -105,45 +109,125 @@
         Objects.requireNonNull(value);
 
         synchronized (mLock) {
-            PolicyState<V> policyState = getLocalPolicyStateLocked(policyDefinition, userId);
+            PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
 
-            boolean policyChanged = policyState.setPolicy(enforcingAdmin, value);
+            boolean hasGlobalPolicies = hasGlobalPolicyLocked(policyDefinition);
+            boolean policyChanged;
+            if (hasGlobalPolicies) {
+                PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
+                policyChanged = localPolicyState.addPolicy(
+                        enforcingAdmin,
+                        value,
+                        globalPolicyState.getPoliciesSetByAdmins());
+            } else {
+                policyChanged = localPolicyState.addPolicy(enforcingAdmin, value);
+            }
 
             if (policyChanged) {
-                enforcePolicy(
-                        policyDefinition, policyState.getCurrentResolvedPolicy(), userId);
-                sendPolicyChangedToAdmins(
-                        policyState.getPoliciesSetByAdmins().keySet(),
-                        enforcingAdmin,
-                        policyDefinition,
-                        userId == enforcingAdmin.getUserId()
-                                ? TargetUser.LOCAL_USER_ID : TargetUser.PARENT_USER_ID);
-
+                onLocalPolicyChanged(policyDefinition, enforcingAdmin, userId);
             }
-            boolean wasAdminPolicyEnforced = Objects.equals(
-                    policyState.getCurrentResolvedPolicy(), value);
+
+            boolean policyEnforced = Objects.equals(
+                    localPolicyState.getCurrentResolvedPolicy(), value);
             sendPolicyResultToAdmin(
                     enforcingAdmin,
                     policyDefinition,
-                    wasAdminPolicyEnforced,
+                    policyEnforced,
                     // TODO: we're always sending this for now, should properly handle errors.
                     REASON_CONFLICTING_ADMIN_POLICY,
-                    userId == enforcingAdmin.getUserId()
-                            ? TargetUser.LOCAL_USER_ID : TargetUser.PARENT_USER_ID);
+                    userId);
 
             write();
-            return policyChanged;
         }
     }
 
     // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
     /**
+     * Removes any previously set policy for the provided {@code policyDefinition}
+     * (see {@link PolicyDefinition}) and {@code enforcingAdmin}.
+     */
+    <V> void removeLocalPolicy(
+            @NonNull PolicyDefinition<V> policyDefinition,
+            @NonNull EnforcingAdmin enforcingAdmin,
+            int userId) {
+        Objects.requireNonNull(policyDefinition);
+        Objects.requireNonNull(enforcingAdmin);
+
+        synchronized (mLock) {
+            if (!hasLocalPolicyLocked(policyDefinition, userId)) {
+                return;
+            }
+            PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
+
+            boolean policyChanged;
+            if (hasGlobalPolicyLocked(policyDefinition)) {
+                PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
+                policyChanged = localPolicyState.removePolicy(
+                        enforcingAdmin,
+                        globalPolicyState.getPoliciesSetByAdmins());
+            } else {
+                policyChanged = localPolicyState.removePolicy(enforcingAdmin);
+            }
+
+            if (policyChanged) {
+                onLocalPolicyChanged(policyDefinition, enforcingAdmin, userId);
+            }
+
+            // For a removePolicy to be enforced, it means no current policy exists
+            boolean policyEnforced = localPolicyState.getCurrentResolvedPolicy() == null;
+            sendPolicyResultToAdmin(
+                    enforcingAdmin,
+                    policyDefinition,
+                    policyEnforced,
+                    // TODO: we're always sending this for now, should properly handle errors.
+                    REASON_CONFLICTING_ADMIN_POLICY,
+                    userId);
+
+            if (localPolicyState.getPoliciesSetByAdmins().isEmpty()) {
+                removeLocalPolicyStateLocked(policyDefinition, userId);
+            }
+
+            write();
+        }
+    }
+
+    /**
+     * Enforces the new policy and notifies relevant admins.
+     */
+    private <V> void onLocalPolicyChanged(
+            @NonNull PolicyDefinition<V> policyDefinition,
+            @NonNull EnforcingAdmin enforcingAdmin,
+            int userId) {
+
+        PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
+        enforcePolicy(
+                policyDefinition, localPolicyState.getCurrentResolvedPolicy(), userId);
+
+        // Send policy updates to admins who've set it locally
+        sendPolicyChangedToAdmins(
+                localPolicyState.getPoliciesSetByAdmins().keySet(),
+                enforcingAdmin,
+                policyDefinition,
+                // This policy change is only relevant to a single user, not the global
+                // policy value,
+                userId);
+
+        // Send policy updates to admins who've set it globally
+        if (hasGlobalPolicyLocked(policyDefinition)) {
+            PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
+            sendPolicyChangedToAdmins(
+                    globalPolicyState.getPoliciesSetByAdmins().keySet(),
+                    enforcingAdmin,
+                    policyDefinition,
+                    userId);
+        }
+    }
+    // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
+    /**
      * Set the policy for the provided {@code policyDefinition}
      * (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}.
-     * Returns {@code true} if the enforced policy has been changed.
-     *
      */
-    <V> boolean setGlobalPolicy(
+    <V> void setGlobalPolicy(
             @NonNull PolicyDefinition<V> policyDefinition,
             @NonNull EnforcingAdmin enforcingAdmin,
             @NonNull V value) {
@@ -153,77 +237,27 @@
         Objects.requireNonNull(value);
 
         synchronized (mLock) {
-            PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
+            PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
 
-
-            boolean policyChanged = policyState.setPolicy(enforcingAdmin, value);
+            boolean policyChanged = globalPolicyState.addPolicy(enforcingAdmin, value);
             if (policyChanged) {
-                enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(),
-                        UserHandle.USER_ALL);
-                sendPolicyChangedToAdmins(
-                        policyState.getPoliciesSetByAdmins().keySet(),
-                        enforcingAdmin,
-                        policyDefinition,
-                        TargetUser.GLOBAL_USER_ID);
+                onGlobalPolicyChanged(policyDefinition, enforcingAdmin);
             }
-            boolean wasAdminPolicyEnforced = Objects.equals(
-                    policyState.getCurrentResolvedPolicy(), value);
+
+            boolean policyEnforcedOnAllUsers = enforceGlobalPolicyOnUsersWithLocalPoliciesLocked(
+                    policyDefinition, enforcingAdmin, value);
+            boolean policyEnforcedGlobally = Objects.equals(
+                    globalPolicyState.getCurrentResolvedPolicy(), value);
+
             sendPolicyResultToAdmin(
                     enforcingAdmin,
                     policyDefinition,
-                    wasAdminPolicyEnforced,
+                    policyEnforcedGlobally && policyEnforcedOnAllUsers,
                     // TODO: we're always sending this for now, should properly handle errors.
                     REASON_CONFLICTING_ADMIN_POLICY,
-                    TargetUser.GLOBAL_USER_ID);
+                    UserHandle.USER_ALL);
 
             write();
-            return policyChanged;
-        }
-    }
-
-
-    // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
-    /**
-     * Removes any previously set policy for the provided {@code policyDefinition}
-     * (see {@link PolicyDefinition}) and {@code enforcingAdmin}.
-     * Returns {@code true} if the enforced policy has been changed.
-     *
-     */
-    <V> boolean removeLocalPolicy(
-            @NonNull PolicyDefinition<V> policyDefinition,
-            @NonNull EnforcingAdmin enforcingAdmin,
-            int userId) {
-
-        Objects.requireNonNull(policyDefinition);
-        Objects.requireNonNull(enforcingAdmin);
-
-        synchronized (mLock) {
-            PolicyState<V> policyState = getLocalPolicyStateLocked(policyDefinition, userId);
-            boolean policyChanged = policyState.removePolicy(enforcingAdmin);
-
-            if (policyChanged) {
-                enforcePolicy(
-                        policyDefinition, policyState.getCurrentResolvedPolicy(), userId);
-                sendPolicyChangedToAdmins(
-                        policyState.getPoliciesSetByAdmins().keySet(),
-                        enforcingAdmin,
-                        policyDefinition,
-                        userId == enforcingAdmin.getUserId()
-                                ? TargetUser.LOCAL_USER_ID : TargetUser.PARENT_USER_ID);
-            }
-            // for a remove policy to be enforced, it means no current policy exists
-            boolean wasAdminPolicyEnforced = policyState.getCurrentResolvedPolicy() == null;
-            sendPolicyResultToAdmin(
-                    enforcingAdmin,
-                    policyDefinition,
-                    wasAdminPolicyEnforced,
-                    // TODO: we're always sending this for now, should properly handle errors.
-                    REASON_CONFLICTING_ADMIN_POLICY,
-                    userId == enforcingAdmin.getUserId()
-                            ? TargetUser.LOCAL_USER_ID : TargetUser.PARENT_USER_ID);
-
-            write();
-            return policyChanged;
         }
     }
 
@@ -231,10 +265,8 @@
     /**
      * Removes any previously set policy for the provided {@code policyDefinition}
      * (see {@link PolicyDefinition}) and {@code enforcingAdmin}.
-     * Returns {@code true} if the enforced policy has been changed.
-     *
      */
-    <V> boolean removeGlobalPolicy(
+    <V> void removeGlobalPolicy(
             @NonNull PolicyDefinition<V> policyDefinition,
             @NonNull EnforcingAdmin enforcingAdmin) {
 
@@ -246,61 +278,168 @@
             boolean policyChanged = policyState.removePolicy(enforcingAdmin);
 
             if (policyChanged) {
-                enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(),
-                        UserHandle.USER_ALL);
-
-                sendPolicyChangedToAdmins(
-                        policyState.getPoliciesSetByAdmins().keySet(),
-                        enforcingAdmin,
-                        policyDefinition,
-                        TargetUser.GLOBAL_USER_ID);
+                onGlobalPolicyChanged(policyDefinition, enforcingAdmin);
             }
-            // for a remove policy to be enforced, it means no current policy exists
-            boolean wasAdminPolicyEnforced = policyState.getCurrentResolvedPolicy() == null;
+
+            boolean policyEnforcedOnAllUsers = enforceGlobalPolicyOnUsersWithLocalPoliciesLocked(
+                    policyDefinition, enforcingAdmin, /* value= */ null);
+            // For a removePolicy to be enforced, it means no current policy exists
+            boolean policyEnforcedGlobally = policyState.getCurrentResolvedPolicy() == null;
+
             sendPolicyResultToAdmin(
                     enforcingAdmin,
                     policyDefinition,
-                    wasAdminPolicyEnforced,
+                    policyEnforcedGlobally && policyEnforcedOnAllUsers,
                     // TODO: we're always sending this for now, should properly handle errors.
                     REASON_CONFLICTING_ADMIN_POLICY,
-                    TargetUser.GLOBAL_USER_ID);
+                    UserHandle.USER_ALL);
+
+            if (policyState.getPoliciesSetByAdmins().isEmpty()) {
+                removeGlobalPolicyStateLocked(policyDefinition);
+            }
 
             write();
-            return policyChanged;
         }
     }
 
     /**
-     * Retrieves policies set by all admins for the provided {@code policyDefinition}.
-     *
+     * Enforces the new policy globally and notifies relevant admins.
      */
-    <V> PolicyState<V> getLocalPolicy(@NonNull PolicyDefinition<V> policyDefinition, int userId) {
+    private <V> void onGlobalPolicyChanged(
+            @NonNull PolicyDefinition<V> policyDefinition,
+            @NonNull EnforcingAdmin enforcingAdmin) {
+        PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
+
+        enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(),
+                UserHandle.USER_ALL);
+
+        sendPolicyChangedToAdmins(
+                policyState.getPoliciesSetByAdmins().keySet(),
+                enforcingAdmin,
+                policyDefinition,
+                UserHandle.USER_ALL);
+    }
+
+    /**
+     * Tries to enforce the global policy locally on all users that have the same policy set
+     * locally, this is only applicable to policies that can be set locally or globally
+     * (e.g. setCameraDisabled, setScreenCaptureDisabled) rather than
+     * policies that are global by nature (e.g. setting Wifi enabled/disabled).
+     *
+     * <p> A {@code null} policy value means the policy was removed
+     *
+     * <p>Returns {@code true} if the policy is enforced successfully on all users.
+     */
+    private <V> boolean enforceGlobalPolicyOnUsersWithLocalPoliciesLocked(
+            @NonNull PolicyDefinition<V> policyDefinition,
+            @NonNull EnforcingAdmin enforcingAdmin,
+            @Nullable V value) {
+        // Global only policies can't be applied locally, return early.
+        if (policyDefinition.isGlobalOnlyPolicy()) {
+            return true;
+        }
+        boolean isAdminPolicyEnforced = true;
+        for (int i = 0; i < mLocalPolicies.size(); i++) {
+            int userId = mLocalPolicies.keyAt(i);
+            if (!hasLocalPolicyLocked(policyDefinition, userId)) {
+                continue;
+            }
+
+            PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
+            PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
+
+            boolean policyChanged = localPolicyState.resolvePolicy(
+                    globalPolicyState.getPoliciesSetByAdmins());
+            if (policyChanged) {
+                enforcePolicy(
+                        policyDefinition, localPolicyState.getCurrentResolvedPolicy(), userId);
+                sendPolicyChangedToAdmins(
+                        localPolicyState.getPoliciesSetByAdmins().keySet(),
+                        enforcingAdmin,
+                        policyDefinition,
+                        // Even though this is caused by a global policy change, admins who've set
+                        // it locally should only care about the local user state.
+                        userId);
+
+            }
+            isAdminPolicyEnforced &= Objects.equals(
+                    value, localPolicyState.getCurrentResolvedPolicy());
+        }
+        return isAdminPolicyEnforced;
+    }
+
+    /**
+     * Retrieves the resolved policy for the provided {@code policyDefinition} and {@code userId}.
+     */
+    @Nullable
+    <V> V getResolvedPolicy(@NonNull PolicyDefinition<V> policyDefinition, int userId) {
         Objects.requireNonNull(policyDefinition);
 
         synchronized (mLock) {
-            return getLocalPolicyStateLocked(policyDefinition, userId);
+            if (hasLocalPolicyLocked(policyDefinition, userId)) {
+                return getLocalPolicyStateLocked(
+                        policyDefinition, userId).getCurrentResolvedPolicy();
+            }
+            if (hasGlobalPolicyLocked(policyDefinition)) {
+                return getGlobalPolicyStateLocked(policyDefinition).getCurrentResolvedPolicy();
+            }
+            return null;
         }
     }
 
     /**
-     * Retrieves policies set by all admins for the provided {@code policyDefinition}.
-     *
+     * Retrieves the policy set by the admin for the provided {@code policyDefinition} and
+     * {@code userId} if one was set, otherwise returns {@code null}.
      */
-    <V> PolicyState<V> getGlobalPolicy(@NonNull PolicyDefinition<V> policyDefinition) {
+    @Nullable
+    <V> V getLocalPolicySetByAdmin(
+            @NonNull PolicyDefinition<V> policyDefinition,
+            @NonNull EnforcingAdmin enforcingAdmin,
+            int userId) {
         Objects.requireNonNull(policyDefinition);
+        Objects.requireNonNull(enforcingAdmin);
 
         synchronized (mLock) {
-            return getGlobalPolicyStateLocked(policyDefinition);
+            if (!hasLocalPolicyLocked(policyDefinition, userId)) {
+                return null;
+            }
+            return getLocalPolicyStateLocked(policyDefinition, userId)
+                    .getPoliciesSetByAdmins().get(enforcingAdmin);
         }
     }
 
+    private <V> boolean hasLocalPolicyLocked(PolicyDefinition<V> policyDefinition, int userId) {
+        if (policyDefinition.isGlobalOnlyPolicy()) {
+            return false;
+        }
+        if (!mLocalPolicies.contains(userId)) {
+            return false;
+        }
+        if (!mLocalPolicies.get(userId).containsKey(policyDefinition.getPolicyKey())) {
+            return false;
+        }
+        return !mLocalPolicies.get(userId).get(policyDefinition.getPolicyKey())
+                .getPoliciesSetByAdmins().isEmpty();
+    }
+
+    private <V> boolean hasGlobalPolicyLocked(PolicyDefinition<V> policyDefinition) {
+        if (policyDefinition.isLocalOnlyPolicy()) {
+            return false;
+        }
+        if (!mGlobalPolicies.containsKey(policyDefinition.getPolicyKey())) {
+            return false;
+        }
+        return !mGlobalPolicies.get(policyDefinition.getPolicyKey()).getPoliciesSetByAdmins()
+                .isEmpty();
+    }
+
     @NonNull
     private <V> PolicyState<V> getLocalPolicyStateLocked(
             PolicyDefinition<V> policyDefinition, int userId) {
 
         if (policyDefinition.isGlobalOnlyPolicy()) {
-            throw new IllegalArgumentException("Can't set global policy "
-                    + policyDefinition.getPolicyKey() + " locally.");
+            throw new IllegalArgumentException(policyDefinition.getPolicyKey() + " is a global only"
+                    + "policy.");
         }
 
         if (!mLocalPolicies.contains(userId)) {
@@ -313,11 +452,19 @@
         return getPolicyState(mLocalPolicies.get(userId), policyDefinition);
     }
 
+    private <V> void removeLocalPolicyStateLocked(
+            PolicyDefinition<V> policyDefinition, int userId) {
+        if (!mLocalPolicies.contains(userId)) {
+            return;
+        }
+        mLocalPolicies.get(userId).remove(policyDefinition.getPolicyKey());
+    }
+
     @NonNull
     private <V> PolicyState<V> getGlobalPolicyStateLocked(PolicyDefinition<V> policyDefinition) {
         if (policyDefinition.isLocalOnlyPolicy()) {
-            throw new IllegalArgumentException("Can't set local policy "
-                    + policyDefinition.getPolicyKey() + " globally.");
+            throw new IllegalArgumentException(policyDefinition.getPolicyKey() + " is a local only"
+                    + "policy.");
         }
 
         if (!mGlobalPolicies.containsKey(policyDefinition.getPolicyKey())) {
@@ -327,6 +474,10 @@
         return getPolicyState(mGlobalPolicies, policyDefinition);
     }
 
+    private <V> void removeGlobalPolicyStateLocked(PolicyDefinition<V> policyDefinition) {
+        mGlobalPolicies.remove(policyDefinition.getPolicyKey());
+    }
+
     private static <V> PolicyState<V> getPolicyState(
             Map<String, PolicyState<?>> policies, PolicyDefinition<V> policyDefinition) {
         try {
@@ -344,14 +495,14 @@
 
     private <V> void enforcePolicy(
             PolicyDefinition<V> policyDefinition, @Nullable V policyValue, int userId) {
-        // TODO: null policyValue means remove any enforced policies, ensure callbacks handle this
-        //  properly
+        // null policyValue means remove any enforced policies, ensure callbacks handle this
+        // properly
         policyDefinition.enforcePolicy(policyValue, mContext, userId);
     }
 
     private <V> void sendPolicyResultToAdmin(
             EnforcingAdmin admin, PolicyDefinition<V> policyDefinition, boolean success,
-            int reason, int targetUserId) {
+            int reason, int userId) {
         Intent intent = new Intent(PolicyUpdatesReceiver.ACTION_DEVICE_POLICY_SET_RESULT);
         intent.setPackage(admin.getPackageName());
 
@@ -367,42 +518,44 @@
 
         Bundle extras = new Bundle();
         extras.putString(EXTRA_POLICY_KEY, policyDefinition.getPolicyDefinitionKey());
-        extras.putInt(EXTRA_POLICY_TARGET_USER_ID, targetUserId);
-
         if (policyDefinition.getCallbackArgs() != null
                 && !policyDefinition.getCallbackArgs().isEmpty()) {
             extras.putBundle(EXTRA_POLICY_BUNDLE_KEY, policyDefinition.getCallbackArgs());
         }
         extras.putInt(
+                EXTRA_POLICY_TARGET_USER_ID,
+                getTargetUser(admin.getUserId(), userId));
+        extras.putInt(
                 EXTRA_POLICY_SET_RESULT_KEY,
                 success ? POLICY_SET_RESULT_SUCCESS : POLICY_SET_RESULT_FAILURE);
 
         if (!success) {
             extras.putInt(EXTRA_POLICY_UPDATE_REASON_KEY, reason);
         }
-
         intent.putExtras(extras);
+
         maybeSendIntentToAdminReceivers(intent, UserHandle.of(admin.getUserId()), receivers);
     }
 
     // TODO(b/261430877): Finalise the decision on which admins to send the updates to.
     private <V> void sendPolicyChangedToAdmins(
-            Set<EnforcingAdmin> admins, EnforcingAdmin callingAdmin,
+            Set<EnforcingAdmin> admins,
+            EnforcingAdmin callingAdmin,
             PolicyDefinition<V> policyDefinition,
-            int targetUserId) {
+            int userId) {
         for (EnforcingAdmin admin: admins) {
             // We're sending a separate broadcast for the calling admin with the result.
             if (admin.equals(callingAdmin)) {
                 continue;
             }
             maybeSendOnPolicyChanged(
-                    admin, policyDefinition, REASON_CONFLICTING_ADMIN_POLICY, targetUserId);
+                    admin, policyDefinition, REASON_CONFLICTING_ADMIN_POLICY, userId);
         }
     }
 
     private <V> void maybeSendOnPolicyChanged(
             EnforcingAdmin admin, PolicyDefinition<V> policyDefinition, int reason,
-            int targetUserId) {
+            int userId) {
         Intent intent = new Intent(PolicyUpdatesReceiver.ACTION_DEVICE_POLICY_CHANGED);
         intent.setPackage(admin.getPackageName());
 
@@ -418,14 +571,16 @@
 
         Bundle extras = new Bundle();
         extras.putString(EXTRA_POLICY_KEY, policyDefinition.getPolicyDefinitionKey());
-        extras.putInt(EXTRA_POLICY_TARGET_USER_ID, targetUserId);
-
         if (policyDefinition.getCallbackArgs() != null
                 && !policyDefinition.getCallbackArgs().isEmpty()) {
             extras.putBundle(EXTRA_POLICY_BUNDLE_KEY, policyDefinition.getCallbackArgs());
         }
+        extras.putInt(
+                EXTRA_POLICY_TARGET_USER_ID,
+                getTargetUser(admin.getUserId(), userId));
         extras.putInt(EXTRA_POLICY_UPDATE_REASON_KEY, reason);
         intent.putExtras(extras);
+
         maybeSendIntentToAdminReceivers(
                 intent, UserHandle.of(admin.getUserId()), receivers);
     }
@@ -447,6 +602,26 @@
         }
     }
 
+    private int getTargetUser(int adminUserId, int targetUserId) {
+        if (targetUserId == UserHandle.USER_ALL) {
+            return TargetUser.GLOBAL_USER_ID;
+        }
+        if (adminUserId == targetUserId) {
+            return TargetUser.LOCAL_USER_ID;
+        }
+        if (getProfileParentId(adminUserId) == targetUserId) {
+            return TargetUser.PARENT_USER_ID;
+        }
+        return TargetUser.UNKNOWN_USER_ID;
+    }
+
+    private int getProfileParentId(int userId) {
+        return Binder.withCleanCallingIdentity(() -> {
+            UserInfo parentUser = mUserManager.getProfileParent(userId);
+            return parentUser != null ? parentUser.id : userId;
+        });
+    }
+
     private void write() {
         Log.d(TAG, "Writing device policies to file.");
         new DevicePoliciesReaderWriter().writeToFileLocked();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 51f3e32..a79fd89 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3300,7 +3300,7 @@
         }
 
         List<String> protectedPackages = (owner == null || owner.protectedPackages == null)
-                ? Collections.emptyList() : owner.protectedPackages;
+                ? null : owner.protectedPackages;
         mInjector.binderWithCleanCallingIdentity(() ->
                 mInjector.getPackageManagerInternal().setOwnerProtectedPackages(
                         targetUserId, protectedPackages));
@@ -12644,9 +12644,10 @@
                         admin,
                         caller.getUserId());
             } else {
-                LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicy(
+                LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicySetByAdmin(
                         PolicyDefinition.LOCK_TASK,
-                        caller.getUserId()).getPoliciesSetByAdmins().get(admin);
+                        admin,
+                        caller.getUserId());
                 LockTaskPolicy policy;
                 if (currentPolicy == null) {
                     policy = new LockTaskPolicy(Set.of(packages));
@@ -12689,8 +12690,8 @@
         }
 
         if (isCoexistenceEnabled(caller)) {
-            LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy(
-                    PolicyDefinition.LOCK_TASK, userHandle).getCurrentResolvedPolicy();
+            LockTaskPolicy policy = mDevicePolicyEngine.getResolvedPolicy(
+                    PolicyDefinition.LOCK_TASK, userHandle);
             if (policy == null) {
                 return new String[0];
             } else {
@@ -12719,8 +12720,8 @@
         // TODO(b/260560985): This is not the right check, as the flag could be enabled but there
         //  could be an admin that hasn't targeted U.
         if (isCoexistenceFlagEnabled()) {
-            LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy(
-                    PolicyDefinition.LOCK_TASK, userId).getCurrentResolvedPolicy();
+            LockTaskPolicy policy = mDevicePolicyEngine.getResolvedPolicy(
+                    PolicyDefinition.LOCK_TASK, userId);
             if (policy == null) {
                 return false;
             }
@@ -12754,9 +12755,10 @@
         }
         if (isCoexistenceEnabled(caller)) {
             EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userHandle);
-            LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicy(
+            LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicySetByAdmin(
                     PolicyDefinition.LOCK_TASK,
-                    caller.getUserId()).getPoliciesSetByAdmins().get(admin);
+                    admin,
+                    caller.getUserId());
             if (currentPolicy == null) {
                 throw new IllegalArgumentException("Can't set a lock task flags without setting "
                         + "lock task packages first.");
@@ -12793,8 +12795,8 @@
         }
 
         if (isCoexistenceEnabled(caller)) {
-            LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy(
-                    PolicyDefinition.LOCK_TASK, userHandle).getCurrentResolvedPolicy();
+            LockTaskPolicy policy = mDevicePolicyEngine.getResolvedPolicy(
+                    PolicyDefinition.LOCK_TASK, userHandle);
             if (policy == null) {
                 // We default on the power button menu, in order to be consistent with pre-P
                 // behaviour.
@@ -17672,12 +17674,20 @@
         checkCanExecuteOrThrowUnsafe(
                 DevicePolicyManager.OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES);
 
-        synchronized (getLockObject()) {
-            ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(caller.getUserId());
-            if (!Objects.equals(owner.protectedPackages, packages)) {
-                owner.protectedPackages = packages.isEmpty() ? null : packages;
-                saveSettingsLocked(caller.getUserId());
-                pushUserControlDisabledPackagesLocked(caller.getUserId());
+        if (isCoexistenceEnabled(caller)) {
+            if (packages.isEmpty()) {
+                removeUserControlDisabledPackages(caller);
+            } else {
+                addUserControlDisabledPackages(caller, new HashSet<>(packages));
+            }
+        } else {
+            synchronized (getLockObject()) {
+                ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(caller.getUserId());
+                if (!Objects.equals(owner.protectedPackages, packages)) {
+                    owner.protectedPackages = packages.isEmpty() ? null : packages;
+                    saveSettingsLocked(caller.getUserId());
+                    pushUserControlDisabledPackagesLocked(caller.getUserId());
+                }
             }
         }
 
@@ -17688,6 +17698,52 @@
                 .write();
     }
 
+    private void addUserControlDisabledPackages(CallerIdentity caller, Set<String> packages) {
+        if (isCallerDeviceOwner(caller)) {
+            mDevicePolicyEngine.setGlobalPolicy(
+                    PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
+                    // TODO(b/260573124): add correct enforcing admin when permission changes are
+                    //  merged.
+                    EnforcingAdmin.createEnterpriseEnforcingAdmin(
+                            caller.getComponentName(), caller.getUserId()),
+                    packages);
+        } else {
+            mDevicePolicyEngine.setLocalPolicy(
+                    PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
+                    // TODO(b/260573124): add correct enforcing admin when permission changes are
+                    //  merged.
+                    EnforcingAdmin.createEnterpriseEnforcingAdmin(
+                            caller.getComponentName(), caller.getUserId()),
+                    packages,
+                    caller.getUserId());
+        }
+    }
+
+    private void removeUserControlDisabledPackages(CallerIdentity caller) {
+        if (isCallerDeviceOwner(caller)) {
+            mDevicePolicyEngine.removeGlobalPolicy(
+                    PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
+                    // TODO(b/260573124): add correct enforcing admin when permission changes are
+                    //  merged.
+                    EnforcingAdmin.createEnterpriseEnforcingAdmin(
+                            caller.getComponentName(), caller.getUserId()));
+        } else {
+            mDevicePolicyEngine.removeLocalPolicy(
+                    PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
+                    // TODO(b/260573124): add correct enforcing admin when permission changes are
+                    //  merged.
+                    EnforcingAdmin.createEnterpriseEnforcingAdmin(
+                            caller.getComponentName(), caller.getUserId()),
+                    caller.getUserId());
+        }
+    }
+
+    private boolean isCallerDeviceOwner(CallerIdentity caller) {
+        synchronized (getLockObject()) {
+            return getDeviceOwnerUserIdUncheckedLocked() == caller.getUserId();
+        }
+    }
+
     @Override
     public List<String> getUserControlDisabledPackages(ComponentName who) {
         Objects.requireNonNull(who, "ComponentName is null");
@@ -17696,10 +17752,19 @@
         Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) || isProfileOwner(caller)
                 || isFinancedDeviceOwner(caller));
 
-        synchronized (getLockObject()) {
-            ActiveAdmin deviceOwner = getDeviceOrProfileOwnerAdminLocked(caller.getUserId());
-            return deviceOwner.protectedPackages != null
-                    ? deviceOwner.protectedPackages : Collections.emptyList();
+        if (isCoexistenceEnabled(caller)) {
+            // This retrieves the policy for the calling user only, DOs for example can't know
+            // what's enforced globally or on another user.
+            Set<String> packages = mDevicePolicyEngine.getResolvedPolicy(
+                    PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
+                    caller.getUserId());
+            return packages == null ? Collections.emptyList() : packages.stream().toList();
+        } else {
+            synchronized (getLockObject()) {
+                ActiveAdmin deviceOwner = getDeviceOrProfileOwnerAdminLocked(caller.getUserId());
+                return deviceOwner.protectedPackages != null
+                        ? deviceOwner.protectedPackages : Collections.emptyList();
+            }
         }
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index 00e48eb..da895f4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -227,4 +227,11 @@
             return new EnforcingAdmin(packageName, componentName, authorities, userId);
         }
     }
+
+    @Override
+    public String toString() {
+        return "EnforcingAdmin { mPackageName= " + mPackageName + ", mComponentName= "
+                + mComponentName + ", mAuthorities= " + mAuthorities + ", mUserId= "
+                + mUserId + ", mIsRoleAuthority= " + mIsRoleAuthority + " }";
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerUnion.java b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerUnion.java
index 00bc261..a051a2b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerUnion.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerUnion.java
@@ -36,4 +36,9 @@
         }
         return unionOfPolicies;
     }
+
+    @Override
+    public String toString() {
+        return "IntegerUnion {}";
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/MostRestrictive.java b/services/devicepolicy/java/com/android/server/devicepolicy/MostRestrictive.java
index 9a24dcf..edb3d2e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/MostRestrictive.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/MostRestrictive.java
@@ -44,4 +44,9 @@
         Map.Entry<EnforcingAdmin, V> policy = adminPolicies.entrySet().stream().findFirst().get();
         return policy.getValue();
     }
+
+    @Override
+    public String toString() {
+        return "MostRestrictive { mMostToLeastRestrictive= " + mMostToLeastRestrictive + " }";
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index c684af3..cfb3db0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -33,6 +33,7 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 final class PolicyDefinition<V> {
     private static final int POLICY_FLAG_NONE = 0;
@@ -98,10 +99,18 @@
                     PolicyEnforcerCallbacks.setLockTask(value, context, userId),
             new LockTaskPolicy.LockTaskPolicySerializer());
 
-    private static Map<String, PolicyDefinition<?>> sPolicyDefinitions = Map.of(
+    static PolicyDefinition<Set<String>> USER_CONTROLLED_DISABLED_PACKAGES = new PolicyDefinition<>(
+            DevicePolicyManager.USER_CONTROL_DISABLED_PACKAGES,
+            new SetUnion<>(),
+            (Set<String> value, Context context, Integer userId, Bundle args) ->
+                    PolicyEnforcerCallbacks.setUserControlDisabledPackages(value, userId),
+            new SetPolicySerializer<>());
+
+    private static final Map<String, PolicyDefinition<?>> sPolicyDefinitions = Map.of(
             DevicePolicyManager.AUTO_TIMEZONE_POLICY, AUTO_TIMEZONE,
             DevicePolicyManager.PERMISSION_GRANT_POLICY_KEY, PERMISSION_GRANT_NO_ARGS,
-            DevicePolicyManager.LOCK_TASK_POLICY, LOCK_TASK
+            DevicePolicyManager.LOCK_TASK_POLICY, LOCK_TASK,
+            DevicePolicyManager.USER_CONTROL_DISABLED_PACKAGES, USER_CONTROLLED_DISABLED_PACKAGES
     );
 
 
@@ -261,4 +270,11 @@
     V readPolicyValueFromXml(TypedXmlPullParser parser, String attributeName) {
         return mPolicySerializer.readFromXml(parser, attributeName);
     }
+
+    @Override
+    public String toString() {
+        return "PolicyDefinition { mPolicyKey= " + mPolicyKey + ", mPolicyDefinitionKey= "
+                + mPolicyDefinitionKey + ", mResolutionMechanism= " + mResolutionMechanism
+                + ", mCallbackArgs= " + mCallbackArgs + ", mPolicyFlags= " + mPolicyFlags + " }";
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index c745b31..5664d2b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -22,6 +22,7 @@
 import android.app.admin.PolicyUpdatesReceiver;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.UserHandle;
@@ -29,11 +30,13 @@
 import android.permission.PermissionControllerManager;
 import android.provider.Settings;
 
+import com.android.server.LocalServices;
 import com.android.server.utils.Slogf;
 
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
@@ -137,4 +140,14 @@
             return mValue.get();
         }
     }
+
+    static boolean setUserControlDisabledPackages(
+            @Nullable Set<String> packages, int userId) {
+        Binder.withCleanCallingIdentity(() ->
+                LocalServices.getService(PackageManagerInternal.class)
+                        .setOwnerProtectedPackages(
+                                userId,
+                                packages == null ? null : packages.stream().toList()));
+        return true;
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index ffde5f8..54d3f54 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -62,12 +62,32 @@
     /**
      * Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
      */
-    boolean setPolicy(@NonNull EnforcingAdmin admin, @NonNull V value) {
-        mPoliciesSetByAdmins.put(Objects.requireNonNull(admin), Objects.requireNonNull(value));
+    boolean addPolicy(@NonNull EnforcingAdmin admin, @NonNull V policy) {
+        mPoliciesSetByAdmins.put(Objects.requireNonNull(admin), Objects.requireNonNull(policy));
 
         return resolvePolicy();
     }
 
+    /**
+     * Takes into account global policies set by the admin when resolving the policy, this is only
+     * relevant to local policies that can be applied globally as well.
+     *
+     * <p> Note that local policies set by an admin takes precedence over global policies set by the
+     * same admin.
+     *
+     * Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
+     */
+    boolean addPolicy(
+            @NonNull EnforcingAdmin admin, @NonNull V policy,
+            LinkedHashMap<EnforcingAdmin, V> globalPoliciesSetByAdmins) {
+        mPoliciesSetByAdmins.put(Objects.requireNonNull(admin), Objects.requireNonNull(policy));
+
+        return resolvePolicy(globalPoliciesSetByAdmins);
+    }
+
+    /**
+     * Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
+     */
     boolean removePolicy(@NonNull EnforcingAdmin admin) {
         Objects.requireNonNull(admin);
 
@@ -78,8 +98,52 @@
         return resolvePolicy();
     }
 
+    /**
+     * Takes into account global policies set by the admin when resolving the policy, this is only
+     * relevant to local policies that can be applied globally as well.
+     *
+     * <p> Note that local policies set by an admin takes precedence over global policies set by the
+     * same admin.
+     *
+     * Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
+     */
+    boolean removePolicy(
+            @NonNull EnforcingAdmin admin,
+            LinkedHashMap<EnforcingAdmin, V> globalPoliciesSetByAdmins) {
+        Objects.requireNonNull(admin);
+
+        if (mPoliciesSetByAdmins.remove(admin) == null) {
+            return false;
+        }
+
+        return resolvePolicy(globalPoliciesSetByAdmins);
+    }
+
+    /**
+     * Takes into account global policies set by the admin when resolving the policy, this is only
+     * relevant to local policies that can be applied globally as well.
+     *
+     * <p> Note that local policies set by an admin takes precedence over global policies set by the
+     * same admin.
+     *
+     * Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
+     */
+    boolean resolvePolicy(LinkedHashMap<EnforcingAdmin, V> globalPoliciesSetByAdmins) {
+        // Add global policies first then override with local policies for the same admin.
+        LinkedHashMap<EnforcingAdmin, V> mergedPolicies =
+                new LinkedHashMap<>(globalPoliciesSetByAdmins);
+        mergedPolicies.putAll(mPoliciesSetByAdmins);
+
+        V resolvedPolicy = mPolicyDefinition.resolvePolicy(mergedPolicies);
+        boolean policyChanged = !Objects.equals(resolvedPolicy, mCurrentResolvedPolicy);
+        mCurrentResolvedPolicy = resolvedPolicy;
+
+        return policyChanged;
+    }
+
+    @NonNull
     LinkedHashMap<EnforcingAdmin, V> getPoliciesSetByAdmins() {
-        return mPoliciesSetByAdmins;
+        return new LinkedHashMap<>(mPoliciesSetByAdmins);
     }
 
     private boolean resolvePolicy() {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SetPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/SetPolicySerializer.java
new file mode 100644
index 0000000..736627b
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/SetPolicySerializer.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 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.devicepolicy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+import java.util.Objects;
+import java.util.Set;
+
+// TODO(scottjonathan): Replace with actual implementation
+final class SetPolicySerializer<V> extends PolicySerializer<Set<V>> {
+
+    @Override
+    void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull Set<V> value)
+            throws IOException {
+        Objects.requireNonNull(value);
+    }
+
+    @Nullable
+    @Override
+    Set<V> readFromXml(TypedXmlPullParser parser, String attributeName) {
+        return null;
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SetUnion.java b/services/devicepolicy/java/com/android/server/devicepolicy/SetUnion.java
index 8a932c3..cf26983 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/SetUnion.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/SetUnion.java
@@ -37,4 +37,9 @@
         }
         return unionOfPolicies;
     }
+
+    @Override
+    public String toString() {
+        return "SetUnion {}";
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/TopPriority.java b/services/devicepolicy/java/com/android/server/devicepolicy/TopPriority.java
index 4467b22..571cf64 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/TopPriority.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/TopPriority.java
@@ -49,4 +49,10 @@
         Map.Entry<EnforcingAdmin, V> policy = adminPolicies.entrySet().stream().findFirst().get();
         return policy.getValue();
     }
+
+    @Override
+    public String toString() {
+        return "TopPriority { mHighestToLowestPriorityAuthorities= "
+                + mHighestToLowestPriorityAuthorities + " }";
+    }
 }