diff --git a/core/api/current.txt b/core/api/current.txt
index 7c7739bfaacde..6d3563133c65e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8160,6 +8160,7 @@ package android.app.admin {
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/DeviceAdminService.java b/core/java/android/app/admin/DeviceAdminService.java
index 04fff0497cb2b..e6f04c64eb3a0 100644
--- a/core/java/android/app/admin/DeviceAdminService.java
+++ b/core/java/android/app/admin/DeviceAdminService.java
@@ -20,6 +20,7 @@ import android.content.ComponentName;
import android.content.Intent;
import android.os.IBinder;
+// TODO(b/263363091): Restrict to DPC and holders of a new role permission and update javadocs
/**
* Base class for a service that device owner/profile owners can optionally have.
*
@@ -45,6 +46,11 @@ import android.os.IBinder;
*
*
Note the process may still be killed if the system is under heavy memory pressure, in which
* case the process will be re-started later.
+ *
+ *
Starting from Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * non-DPC admins can also optionally implement this service using the details
+ * mentioned above to ensure they receive policy update broadcasts
+ * (see {@link PolicyUpdatesReceiver}).
*/
public class DeviceAdminService extends Service {
private final IDeviceAdminServiceImpl mImpl;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 500823a36310f..de03ba40f3156 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -4005,6 +4005,12 @@ public class DevicePolicyManager {
*/
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/PolicyUpdatesReceiver.java b/core/java/android/app/admin/PolicyUpdatesReceiver.java
index ff30a5f8a037f..3ad315753a29d 100644
--- a/core/java/android/app/admin/PolicyUpdatesReceiver.java
+++ b/core/java/android/app/admin/PolicyUpdatesReceiver.java
@@ -32,7 +32,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
-// TODO(b/261432333): Add more detailed javadocs on using DeviceAdminService.
/**
* Base class for implementing a policy update receiver. This class provides a convenience for
* interpreting the raw intent actions ({@link #ACTION_DEVICE_POLICY_SET_RESULT} and
@@ -43,6 +42,10 @@ import java.util.Objects;
*
*
When publishing your {@code PolicyUpdatesReceiver} subclass as a receiver, it must
* require the {@link android.Manifest.permission#BIND_DEVICE_ADMIN} permission.
+ *
+ *
Admins can implement {@link DeviceAdminService} to ensure they receive all policy updates
+ * (for policies they have set) via {@link #onPolicyChanged} by constantly being bound to by the
+ * system. For more information see {@link DeviceAdminService}.
*/
public abstract class PolicyUpdatesReceiver extends BroadcastReceiver {
private static String TAG = "PolicyUpdatesReceiver";
diff --git a/core/java/android/app/admin/TargetUser.java b/core/java/android/app/admin/TargetUser.java
index acbac29dabe6f..1ec2d5225f4a3 100644
--- a/core/java/android/app/admin/TargetUser.java
+++ b/core/java/android/app/admin/TargetUser.java
@@ -43,6 +43,11 @@ public final class TargetUser {
*/
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.
*/
@@ -61,6 +66,15 @@ public final class TargetUser {
@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/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 5f5327da412f4..f101e7364c31f 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -375,10 +375,15 @@ public abstract class PackageManagerInternal {
int deviceOwnerUserId, String deviceOwner, SparseArray profileOwners);
/**
- * Marks packages as protected for a given user or all users in case of USER_ALL.
+ * Marks packages as protected for a given user or all users in case of USER_ALL. Setting
+ * {@code packageNames} to {@code null} means unset all existing protected packages for the
+ * given user.
+ *
+ * Note that setting it if set for a specific user, it takes precedence over the packages
+ * set globally using USER_ALL.
*/
public abstract void setOwnerProtectedPackages(
- @UserIdInt int userId, @NonNull List packageNames);
+ @UserIdInt int userId, @Nullable List packageNames);
/**
* Returns {@code true} if a given package can't be wiped. Otherwise, returns {@code false}.
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index cc9c1e0ac1799..fb47c8aabbf31 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -354,7 +354,7 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal {
@Override
@Deprecated
public final void setOwnerProtectedPackages(
- @UserIdInt int userId, @NonNull List packageNames) {
+ @UserIdInt int userId, @Nullable List packageNames) {
getProtectedPackages().setOwnerProtectedPackages(userId, packageNames);
}
diff --git a/services/core/java/com/android/server/pm/ProtectedPackages.java b/services/core/java/com/android/server/pm/ProtectedPackages.java
index e9239889973a0..98533725371f7 100644
--- a/services/core/java/com/android/server/pm/ProtectedPackages.java
+++ b/services/core/java/com/android/server/pm/ProtectedPackages.java
@@ -16,7 +16,6 @@
package com.android.server.pm;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
@@ -81,8 +80,8 @@ public class ProtectedPackages {
/** Sets packages protected by a device or profile owner. */
public synchronized void setOwnerProtectedPackages(
- @UserIdInt int userId, @NonNull List packageNames) {
- if (packageNames.isEmpty()) {
+ @UserIdInt int userId, @Nullable List packageNames) {
+ if (packageNames == null) {
mOwnerProtectedPackages.remove(userId);
} else {
mOwnerProtectedPackages.put(userId, new ArraySet<>(packageNames));
@@ -134,15 +133,21 @@ public class ProtectedPackages {
*/
private synchronized boolean isOwnerProtectedPackage(
@UserIdInt int userId, String packageName) {
- return isPackageProtectedForUser(UserHandle.USER_ALL, packageName)
- || isPackageProtectedForUser(userId, packageName);
+ return hasProtectedPackages(userId)
+ ? isPackageProtectedForUser(userId, packageName)
+ : isPackageProtectedForUser(UserHandle.USER_ALL, packageName);
}
- private synchronized boolean isPackageProtectedForUser(int userId, String packageName) {
+ private synchronized boolean isPackageProtectedForUser(
+ @UserIdInt int userId, String packageName) {
int userIdx = mOwnerProtectedPackages.indexOfKey(userId);
return userIdx >= 0 && mOwnerProtectedPackages.valueAt(userIdx).contains(packageName);
}
+ private synchronized boolean hasProtectedPackages(@UserIdInt int userId) {
+ return mOwnerProtectedPackages.indexOfKey(userId) >= 0;
+ }
+
/**
* Returns {@code true} if a given package's state is protected. Otherwise, returns
* {@code false}.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java
index 8f0af918cb53b..e080fc7a18544 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java
@@ -35,8 +35,11 @@ import com.android.server.am.PersistentConnection;
import com.android.server.appbinding.AppBindingUtils;
import com.android.server.utils.Slogf;
+import java.util.HashMap;
+import java.util.Map;
+
/**
- * Manages connections to persistent services in owner packages.
+ * Manages connections to persistent services in admin packages.
*/
public class DeviceAdminServiceController {
static final String TAG = DevicePolicyManagerService.LOG_TAG;
@@ -76,7 +79,8 @@ public class DeviceAdminServiceController {
* User-ID -> {@link PersistentConnection}.
*/
@GuardedBy("mLock")
- private final SparseArray mConnections = new SparseArray<>();
+ private final SparseArray