Add getBindDeviceAdminTargetUsers API
Add getBindDeviceAdminTargetUsers API. To be used
for the device or profile owner in a COMP setup to
know the UserHandle of their instance running on
the other profile.
Bug: 31952368,33033966
Test: cts-tradefed run cts --module DevicePolicyManager --test com.android.cts.devicepolicy.DeviceOwnerPlusManagedProfileTest
Change-Id: I05c9ac0f6c3cdce1ec17cf2910ca7a7214064af2
diff --git a/api/current.txt b/api/current.txt
index 8a00273..739b2a1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6000,6 +6000,7 @@
method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
method public boolean getAutoTimeRequired();
+ method public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(android.content.ComponentName);
method public boolean getBluetoothContactSharingDisabled(android.content.ComponentName);
method public boolean getCameraDisabled(android.content.ComponentName);
method public java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
diff --git a/api/system-current.txt b/api/system-current.txt
index 3ecc8aa..30af876 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6172,6 +6172,7 @@
method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
method public boolean getAutoTimeRequired();
+ method public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(android.content.ComponentName);
method public boolean getBluetoothContactSharingDisabled(android.content.ComponentName);
method public boolean getCameraDisabled(android.content.ComponentName);
method public java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
diff --git a/api/test-current.txt b/api/test-current.txt
index 13b0c94..ed27437 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6016,6 +6016,7 @@
method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
method public boolean getAutoTimeRequired();
+ method public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(android.content.ComponentName);
method public boolean getBluetoothContactSharingDisabled(android.content.ComponentName);
method public boolean getCameraDisabled(android.content.ComponentName);
method public java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 0196312..cae4210 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6715,11 +6715,14 @@
}
/**
- * Called by device owner/ profile owner in managed profile to bind the service with each other.
+ * Called by a device owner to bind to a service from a profile owner of a managed profile or
+ * vice versa. See {@link #getBindDeviceAdminTargetUsers} for a definition of which
+ * device/profile owners are allowed to bind to services of another profile/device owner.
+ * <p>
* The service must be unexported. Note that the {@link Context} used to obtain this
* {@link DevicePolicyManager} instance via {@link Context#getSystemService(Class)} will be used
* to bind to the {@link android.app.Service}.
- * STOPSHIP (b/31952368): Update the javadoc after we policy to control which packages can talk.
+ *
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param serviceIntent Identifies the service to connect to. The Intent must specify either an
* explicit component name or a package name to match an
@@ -6728,11 +6731,15 @@
* valid {@link ServiceConnection} object; it must not be {@code null}.
* @param flags Operation options for the binding operation. See
* {@link Context#bindService(Intent, ServiceConnection, int)}.
- * @param targetUser Which user to bind to.
+ * @param targetUser Which user to bind to. Must be one of the users returned by
+ * {@link #getBindDeviceAdminTargetUsers}, otherwise a {@link SecurityException} will
+ * be thrown.
* @return If you have successfully bound to the service, {@code true} is returned;
* {@code false} is returned if the connection is not made and you will not
* receive the service object.
+ *
* @see Context#bindService(Intent, ServiceConnection, int)
+ * @see #getBindDeviceAdminTargetUsers(ComponentName)
*/
public boolean bindDeviceAdminServiceAsUser(
@NonNull ComponentName admin, Intent serviceIntent, @NonNull ServiceConnection conn,
@@ -6751,6 +6758,29 @@
}
/**
+ * Returns the list of target users that the calling device or profile owner can use when
+ * calling {@link #bindDeviceAdminServiceAsUser}.
+ * <p>
+ * A device owner can bind to a service from a profile owner of a managed profile and
+ * vice versa, provided that:
+ * <ul>
+ * <li>Both belong to the same package name.
+ * <li>The managed profile is a profile of the user where the device owner is set.
+ * See {@link UserManager#getUserProfiles()}
+ * <li>Both users are affiliated.
+ * STOPSHIP(b/32326223) Add reference to setAffiliationIds here once public.
+ * </ul>
+ */
+ public @NonNull List<UserHandle> getBindDeviceAdminTargetUsers(@NonNull ComponentName admin) {
+ throwIfParentInstance("getBindDeviceAdminTargetUsers");
+ try {
+ return mService.getBindDeviceAdminTargetUsers(admin);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Called by the system to get the time at which the device owner last retrieved security
* logging entries.
*
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index d14e0d0..b7e0e92 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -325,6 +325,7 @@
boolean bindDeviceAdminServiceAsUser(in ComponentName admin,
IApplicationThread caller, IBinder token, in Intent service,
IServiceConnection connection, int flags, int targetUserId);
+ List<UserHandle> getBindDeviceAdminTargetUsers(in ComponentName admin);
long getLastSecurityLogRetrievalTime();
long getLastBugReportRequestTime();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c497cb1..cf28269 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -6634,7 +6634,8 @@
}
private boolean isManagedProfile(int userHandle) {
- return getUserInfo(userHandle).isManagedProfile();
+ final UserInfo user = getUserInfo(userHandle);
+ return user != null && user.isManagedProfile();
}
private void enableIfNecessary(String packageName, int userId) {
@@ -8903,9 +8904,7 @@
synchronized (this) {
getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
}
- final int callingUserId = mInjector.userHandleGetCallingUserId();
- final UserInfo user = getUserInfo(callingUserId);
- return user != null && user.isManagedProfile();
+ return isManagedProfile(mInjector.userHandleGetCallingUserId());
}
@Override
@@ -9522,65 +9521,97 @@
Preconditions.checkNotNull(caller);
Preconditions.checkNotNull(serviceIntent);
Preconditions.checkNotNull(connection);
- final int callingUserId = mInjector.userHandleGetCallingUserId();
- Preconditions.checkArgument(callingUserId != targetUserId,
+ Preconditions.checkArgument(mInjector.userHandleGetCallingUserId() != targetUserId,
"target user id must be different from the calling user id");
- synchronized (this) {
- final ActiveAdmin callingAdmin = getActiveAdminForCallerLocked(admin,
- DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- // Ensure the target user is valid.
- if (isDeviceOwner(callingAdmin)) {
- enforceManagedProfile(targetUserId, "Target user must be a managed profile");
- } else {
- // Further lock down to profile owner in managed profile.
- enforceManagedProfile(callingUserId,
- "Only support profile owner in managed profile.");
- if (mOwners.getDeviceOwnerUserId() != targetUserId) {
- throw new SecurityException("Target user must be a device owner.");
- }
- }
+ if (!getBindDeviceAdminTargetUsers(admin).contains(UserHandle.of(targetUserId))) {
+ throw new SecurityException("Not allowed to bind to target user id");
}
+
+ final String targetPackage;
+ synchronized (this) {
+ targetPackage = getOwnerPackageNameForUserLocked(targetUserId);
+ }
+
final long callingIdentity = mInjector.binderClearCallingIdentity();
try {
- if (!mUserManager.isSameProfileGroup(callingUserId, targetUserId)) {
- throw new SecurityException(
- "Can only bind service across users under the same profile group");
- }
- final String targetPackage;
- synchronized (this) {
- targetPackage = getOwnerPackageNameForUserLocked(targetUserId);
- }
- // STOPSHIP(b/31952368): Add policy to control which packages can talk.
- if (TextUtils.isEmpty(targetPackage) || !targetPackage.equals(admin.getPackageName())) {
- throw new SecurityException("Device owner and profile owner must be the same " +
- "package in order to communicate.");
- }
// Validate and sanitize the incoming service intent.
final Intent sanitizedIntent =
- createCrossUserServiceIntent(serviceIntent, targetPackage);
+ createCrossUserServiceIntent(serviceIntent, targetPackage, targetUserId);
if (sanitizedIntent == null) {
// Fail, cannot lookup the target service.
throw new SecurityException("Invalid intent or failed to look up the service");
}
+
// Ask ActivityManager to bind it. Notice that we are binding the service with the
// caller app instead of DevicePolicyManagerService.
- try {
- return mInjector.getIActivityManager().bindService(
- caller, activtiyToken, serviceIntent,
- serviceIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
- connection, flags, mContext.getOpPackageName(),
- targetUserId) != 0;
- } catch (RemoteException ex) {
- // Same process, should not happen.
- }
+ return mInjector.getIActivityManager().bindService(
+ caller, activtiyToken, serviceIntent,
+ serviceIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ connection, flags, mContext.getOpPackageName(),
+ targetUserId) != 0;
+ } catch (RemoteException ex) {
+ // Same process, should not happen.
} finally {
mInjector.binderRestoreCallingIdentity(callingIdentity);
}
- // Fail to bind.
+
+ // Failed to bind.
return false;
}
+ @Override
+ public @NonNull List<UserHandle> getBindDeviceAdminTargetUsers(@NonNull ComponentName admin) {
+ if (!mHasFeature) {
+ return Collections.emptyList();
+ }
+ Preconditions.checkNotNull(admin);
+ ArrayList<UserHandle> targetUsers = new ArrayList<>();
+
+ synchronized (this) {
+ ActiveAdmin callingOwner = getActiveAdminForCallerLocked(
+ admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+
+ final int callingUserId = mInjector.userHandleGetCallingUserId();
+ final boolean isCallerDeviceOwner = isDeviceOwner(callingOwner);
+ final boolean isCallerManagedProfile = isManagedProfile(callingUserId);
+ if (!isCallerDeviceOwner && !isCallerManagedProfile
+ /* STOPSHIP(b/32326223) Reinstate when setAffiliationIds is public
+ || !isAffiliatedUser(callingUserId) */) {
+ return targetUsers;
+ }
+
+ final long callingIdentity = mInjector.binderClearCallingIdentity();
+ try {
+ String callingOwnerPackage = callingOwner.info.getComponent().getPackageName();
+ for (int userId : mUserManager.getProfileIds(
+ callingUserId, /* enabledOnly= */ false)) {
+ if (userId == callingUserId) {
+ continue;
+ }
+
+ // We only allow the device owner and a managed profile owner to bind to each
+ // other.
+ if ((isCallerManagedProfile && userId == mOwners.getDeviceOwnerUserId())
+ || (isCallerDeviceOwner && isManagedProfile(userId))) {
+ String targetOwnerPackage = getOwnerPackageNameForUserLocked(userId);
+
+ // Both must be the same package and be affiliated in order to bind.
+ if (callingOwnerPackage.equals(targetOwnerPackage)
+ /* STOPSHIP(b/32326223) Reinstate when setAffiliationIds is public
+ && isAffiliatedUser(userId)*/) {
+ targetUsers.add(UserHandle.of(userId));
+ }
+ }
+ }
+ } finally {
+ mInjector.binderRestoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ return targetUsers;
+ }
+
/**
* Return true if a given user has any accounts that'll prevent installing a device or profile
* owner {@code owner}.
@@ -9761,7 +9792,7 @@
* Return the package name of owner in a given user.
*/
private String getOwnerPackageNameForUserLocked(int userId) {
- return getDeviceOwnerUserId() == userId
+ return mOwners.getDeviceOwnerUserId() == userId
? mOwners.getDeviceOwnerPackageName()
: mOwners.getProfileOwnerPackage(userId);
}
@@ -9772,14 +9803,19 @@
* @return Intent that have component explicitly set. {@code null} if the incoming intent
* or target service is invalid.
*/
- private Intent createCrossUserServiceIntent (
- @NonNull Intent rawIntent, @NonNull String expectedPackageName) {
+ private Intent createCrossUserServiceIntent(
+ @NonNull Intent rawIntent, @NonNull String expectedPackageName,
+ @UserIdInt int targetUserId) throws RemoteException {
if (rawIntent.getComponent() == null && rawIntent.getPackage() == null) {
Log.e(LOG_TAG, "Service intent must be explicit (with a package name or component): "
+ rawIntent);
return null;
}
- ResolveInfo info = mInjector.getPackageManager().resolveService(rawIntent, 0);
+ ResolveInfo info = mIPackageManager.resolveService(
+ rawIntent,
+ rawIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ 0, // flags
+ targetUserId);
if (info == null || info.serviceInfo == null) {
Log.e(LOG_TAG, "Fail to look up the service: " + rawIntent);
return null;