Always sync user restriction state to UserManager

On certain Android 14 builds, global restrictions set by the
DPC can errornously go into UserManager's local restriction
store, resulting in a bad state where they cannot be unset
in future. Fix this by always force syncing user restrictions
from the policy engine to the UserManager when a restriction is
being set or cleared by the DPC. The force sync ensures that both
local and global restriction states of UserManager are consistent
with policy engine, for the restriction that is being updated.

Ran through the following manual tests:
1. (DO) Start with A14 bad build, set global restrictions
   -> OTA to this fix
   -> try clearing policy
2. (DO) Start with A14 bad build, set global restrictions
   -> reboot device
   -> OTA to this fix
   -> try clearing policy
3. (DO) A13 with local & global restrictions set
   -> OTA to A14 bad build
   -> set same restrictions again
   -> OTA to this fix
   -> try clearing policy
4. (DO) A13 with local & global restrictions set
   -> OTA to A14 bad build
   -> reboot device
   -> set same restrictions again
   -> OTA to this fix
   -> try clearing policy
5. (DO) A13 with local & global restrictions set
   -> OTA to A14 bad build
   -> OTA to this fix
   -> try clearing policy
6. (DO) A13 with local & global restrictions set
   -> OTA to A14 bad build
   -> reboot device
   -> OTA to this fix
   -> try clearing policy
7. (DO) A13 with local & global restrictions set
   -> OTA to this fix
   -> try clearing policy
8. (BYOD PO) A13 with global restrictions set
   -> OTA to A14 bad build
   -> reboot device
   -> OTA to this fix
   -> try clearing policy
Case 1 & 2: fresh A14 setup
Case 3 & 4: OTA'ed devices with workaround applied
Case 5 & 6: OTA'ed devices without workaround applied
Case 7: directly OTA'ed to build with fix
Case 8: same as case 5 & 6 but on a BYOD PO device

Bug: 311687929
Bug: 299302474
Test: btest a.d.c.PolicyEngineTest
Test: atest android.devicepolicy.cts.UserRestrictionsTest
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:5394ddbee5dd88a35e2a9a8508dc260395895ac1)
Merged-In: I4d700bc42ec114d1c0fc86f230f7f7612819195c
Change-Id: I4d700bc42ec114d1c0fc86f230f7f7612819195c
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 9c1d765..9f65a33 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -30,6 +30,7 @@
 import android.annotation.Nullable;
 import android.app.AppGlobals;
 import android.app.BroadcastOptions;
+import android.app.admin.BooleanPolicyValue;
 import android.app.admin.DevicePolicyIdentifiers;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyState;
@@ -133,6 +134,67 @@
         mEnforcingAdmins = new SparseArray<>();
     }
 
+    private void maybeForceEnforcementRefreshLocked(@NonNull PolicyDefinition<?> policyDefinition) {
+        try {
+            if (shouldForceEnforcementRefresh(policyDefinition)) {
+                // This is okay because it's only true for user restrictions which are all <Boolean>
+                forceEnforcementRefreshLocked((PolicyDefinition<Boolean>) policyDefinition);
+            }
+        } catch (Throwable e) {
+            // Catch any possible exceptions just to be on the safe side
+            Log.e(TAG, "Exception throw during maybeForceEnforcementRefreshLocked", e);
+        }
+    }
+
+    private boolean shouldForceEnforcementRefresh(@NonNull PolicyDefinition<?> policyDefinition) {
+        // These are all "not nullable" but for the purposes of maximum safety for a lightly tested
+        // change we check here
+        if (policyDefinition == null) {
+            return false;
+        }
+        PolicyKey policyKey = policyDefinition.getPolicyKey();
+        if (policyKey == null) {
+            return false;
+        }
+
+        if (policyKey instanceof UserRestrictionPolicyKey) {
+            // b/307481299 We must force all user restrictions to re-sync local
+            // + global on each set/clear
+            return true;
+        }
+
+        return false;
+    }
+
+    private void forceEnforcementRefreshLocked(PolicyDefinition<Boolean> policyDefinition) {
+        Binder.withCleanCallingIdentity(() -> {
+            // Sync global state
+            PolicyValue<Boolean> globalValue = new BooleanPolicyValue(false);
+            try {
+                PolicyState<Boolean> policyState = getGlobalPolicyStateLocked(policyDefinition);
+                globalValue = policyState.getCurrentResolvedPolicy();
+            } catch (IllegalArgumentException e) {
+                // Expected for local-only policies
+            }
+
+            enforcePolicy(policyDefinition, globalValue, UserHandle.USER_ALL);
+
+            // Loop through each user and sync that user's state
+            for (UserInfo user : mUserManager.getUsers()) {
+                PolicyValue<Boolean> localValue = new BooleanPolicyValue(false);
+                try {
+                    PolicyState<Boolean> localPolicyState = getLocalPolicyStateLocked(
+                            policyDefinition, user.id);
+                    localValue = localPolicyState.getCurrentResolvedPolicy();
+                } catch (IllegalArgumentException e) {
+                    // Expected for global-only policies
+                }
+
+                enforcePolicy(policyDefinition, localValue, user.id);
+            }
+        });
+    }
+
     /**
      * Set the policy for the provided {@code policyDefinition} (see {@link PolicyDefinition}) and
      * {@code enforcingAdmin} to the provided {@code value}.
@@ -174,6 +236,7 @@
             // No need to notify admins as no new policy is actually enforced, we're just filling in
             // the data structures.
             if (!skipEnforcePolicy) {
+                maybeForceEnforcementRefreshLocked(policyDefinition);
                 if (policyChanged) {
                     onLocalPolicyChangedLocked(policyDefinition, enforcingAdmin, userId);
                 }
@@ -262,6 +325,7 @@
         Objects.requireNonNull(enforcingAdmin);
 
         synchronized (mLock) {
+            maybeForceEnforcementRefreshLocked(policyDefinition);
             if (!hasLocalPolicyLocked(policyDefinition, userId)) {
                 return;
             }
@@ -425,6 +489,7 @@
             // No need to notify admins as no new policy is actually enforced, we're just filling in
             // the data structures.
             if (!skipEnforcePolicy) {
+                maybeForceEnforcementRefreshLocked(policyDefinition);
                 if (policyChanged) {
                     onGlobalPolicyChangedLocked(policyDefinition, enforcingAdmin);
                 }
@@ -474,6 +539,7 @@
             PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
             boolean policyChanged = policyState.removePolicy(enforcingAdmin);
 
+            maybeForceEnforcementRefreshLocked(policyDefinition);
             if (policyChanged) {
                 onGlobalPolicyChangedLocked(policyDefinition, enforcingAdmin);
             }