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 + " }";
+ }
}