Allow control over account management of parent profile
Let the owner of a managed profile on an organization-owned device set
accounts for which management is disabled in the primary profile, via
the parent profile's DevicePolicyManager instance.
Test: atest CtsDevicePolicyManagerTestCases:com.android.cts.devicepolicy.OrgOwnedProfileOwnerTest#testCanRestrictAccountManagementOnParentProfile
Test: atest FrameworksServicesTests:DevicePolicyManagerTest
Bug: 148438071
Change-Id: I45eaf5e8e403e0c23dad2df106fefd1a1f3c6f4b
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index dc11013..d724a18 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -8463,6 +8463,11 @@
* From {@link android.os.Build.VERSION_CODES#N} the profile or device owner can still use
* {@link android.accounts.AccountManager} APIs to add or remove accounts when account
* management for a specific type is disabled.
+ * <p>
+ * This method may be called on the {@code DevicePolicyManager} instance returned from
+ * {@link #getParentProfileInstance(ComponentName)} by the profile owner on an
+ * organization-owned device, to restrict accounts that may not be managed on the primary
+ * profile.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param accountType For which account management is disabled or enabled.
@@ -8472,10 +8477,10 @@
*/
public void setAccountManagementDisabled(@NonNull ComponentName admin, String accountType,
boolean disabled) {
- throwIfParentInstance("setAccountManagementDisabled");
if (mService != null) {
try {
- mService.setAccountManagementDisabled(admin, accountType, disabled);
+ mService.setAccountManagementDisabled(admin, accountType, disabled,
+ mParentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -8483,28 +8488,43 @@
}
/**
- * Gets the array of accounts for which account management is disabled by the profile owner.
+ * Gets the array of accounts for which account management is disabled by the profile owner
+ * or device owner.
*
* <p> Account management can be disabled/enabled by calling
* {@link #setAccountManagementDisabled}.
+ * <p>
+ * This method may be called on the {@code DevicePolicyManager} instance returned from
+ * {@link #getParentProfileInstance(ComponentName)}. Note that only a profile owner on
+ * an organization-deviced can affect account types on the parent profile instance.
*
* @return a list of account types for which account management has been disabled.
*
* @see #setAccountManagementDisabled
*/
public @Nullable String[] getAccountTypesWithManagementDisabled() {
- throwIfParentInstance("getAccountTypesWithManagementDisabled");
- return getAccountTypesWithManagementDisabledAsUser(myUserId());
+ return getAccountTypesWithManagementDisabledAsUser(myUserId(), mParentInstance);
+ }
+
+ /**
+ * @see #getAccountTypesWithManagementDisabled()
+ * Note that calling this method on the parent profile instance will return the same
+ * value as calling it on the main {@code DevicePolicyManager} instance.
+ * @hide
+ */
+ public @Nullable String[] getAccountTypesWithManagementDisabledAsUser(int userId) {
+ return getAccountTypesWithManagementDisabledAsUser(userId, false);
}
/**
* @see #getAccountTypesWithManagementDisabled()
* @hide
*/
- public @Nullable String[] getAccountTypesWithManagementDisabledAsUser(int userId) {
+ public @Nullable String[] getAccountTypesWithManagementDisabledAsUser(
+ int userId, boolean parentInstance) {
if (mService != null) {
try {
- return mService.getAccountTypesWithManagementDisabledAsUser(userId);
+ return mService.getAccountTypesWithManagementDisabledAsUser(userId, parentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -9842,6 +9862,7 @@
* <li>{@link #setTrustAgentConfiguration}</li>
* <li>{@link #getRequiredStrongAuthTimeout}</li>
* <li>{@link #setRequiredStrongAuthTimeout}</li>
+ * <li>{@link #getAccountTypesWithManagementDisabled}</li>
* </ul>
* <p>
* The following methods are supported for the parent instance but can only be called by the
@@ -9850,6 +9871,7 @@
* <li>{@link #getPasswordComplexity}</li>
* <li>{@link #setCameraDisabled}</li>
* <li>{@link #getCameraDisabled}</li>
+ * <li>{@link #setAccountManagementDisabled(ComponentName, String, boolean)}</li>
* </ul>
*
* <p>The following methods can be called by the profile owner of a managed profile
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 0aed39c..84332ca 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -248,9 +248,9 @@
int enableSystemAppWithIntent(in ComponentName admin, in String callerPackage, in Intent intent);
boolean installExistingPackage(in ComponentName admin, in String callerPackage, in String packageName);
- void setAccountManagementDisabled(in ComponentName who, in String accountType, in boolean disabled);
+ void setAccountManagementDisabled(in ComponentName who, in String accountType, in boolean disabled, in boolean parent);
String[] getAccountTypesWithManagementDisabled();
- String[] getAccountTypesWithManagementDisabledAsUser(int userId);
+ String[] getAccountTypesWithManagementDisabledAsUser(int userId, in boolean parent);
void setSecondaryLockscreenEnabled(in ComponentName who, boolean enabled);
boolean isSecondaryLockscreenEnabled(int userId);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e469067..aa9f90f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -11049,14 +11049,21 @@
@Override
public void setAccountManagementDisabled(ComponentName who, String accountType,
- boolean disabled) {
+ boolean disabled, boolean parent) {
if (!mHasFeature) {
return;
}
Objects.requireNonNull(who, "ComponentName is null");
synchronized (getLockObject()) {
+ /*
+ * When called on the parent DPM instance (parent == true), affects active admin
+ * selection in two ways:
+ * * The ActiveAdmin must be of an org-owned profile owner.
+ * * The parent ActiveAdmin instance should be used for managing the restriction.
+ */
ActiveAdmin ap = getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ parent ? DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER
+ : DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, parent);
if (disabled) {
ap.accountTypesWithManagementDisabled.add(accountType);
} else {
@@ -11068,22 +11075,34 @@
@Override
public String[] getAccountTypesWithManagementDisabled() {
- return getAccountTypesWithManagementDisabledAsUser(UserHandle.getCallingUserId());
+ return getAccountTypesWithManagementDisabledAsUser(UserHandle.getCallingUserId(), false);
}
@Override
- public String[] getAccountTypesWithManagementDisabledAsUser(int userId) {
+ public String[] getAccountTypesWithManagementDisabledAsUser(int userId, boolean parent) {
enforceFullCrossUsersPermission(userId);
if (!mHasFeature) {
return null;
}
synchronized (getLockObject()) {
- DevicePolicyData policy = getUserData(userId);
- final int N = policy.mAdminList.size();
- ArraySet<String> resultSet = new ArraySet<>();
- for (int i = 0; i < N; i++) {
- ActiveAdmin admin = policy.mAdminList.get(i);
- resultSet.addAll(admin.accountTypesWithManagementDisabled);
+ final ArraySet<String> resultSet = new ArraySet<>();
+
+ if (!parent) {
+ final DevicePolicyData policy = getUserData(userId);
+ for (ActiveAdmin admin : policy.mAdminList) {
+ resultSet.addAll(admin.accountTypesWithManagementDisabled);
+ }
+ }
+
+ // Check if there's a profile owner of an org-owned device and the method is called for
+ // the parent user of this profile owner.
+ final ActiveAdmin orgOwnedAdmin =
+ getProfileOwnerOfOrganizationOwnedDeviceLocked(userId);
+ final boolean shouldGetParentAccounts = orgOwnedAdmin != null && (parent
+ || UserHandle.getUserId(orgOwnedAdmin.getUid()) != userId);
+ if (shouldGetParentAccounts) {
+ resultSet.addAll(
+ orgOwnedAdmin.getParentActiveAdmin().accountTypesWithManagementDisabled);
}
return resultSet.toArray(new String[resultSet.size()]);
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index f2f8ad1..fa50441 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -96,7 +96,6 @@
import android.util.ArraySet;
import android.util.Pair;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
@@ -6022,6 +6021,55 @@
.thenReturn(packages);
}
+ public void testSetAccountTypesWithManagementDisabledOnManagedProfile() throws Exception {
+ setupProfileOwner();
+
+ final String accountType = "com.example.account.type";
+ int originalUid = mContext.binder.callingUid;
+ dpm.setAccountManagementDisabled(admin1, accountType, true);
+ assertThat(dpm.getAccountTypesWithManagementDisabled()).asList().containsExactly(
+ accountType);
+ mContext.binder.callingUid = DpmMockContext.ANOTHER_UID;
+ assertThat(dpm.getAccountTypesWithManagementDisabled()).isEmpty();
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ assertThat(dpm.getAccountTypesWithManagementDisabled()).isEmpty();
+
+ mContext.binder.callingUid = originalUid;
+ dpm.setAccountManagementDisabled(admin1, accountType, false);
+ assertThat(dpm.getAccountTypesWithManagementDisabled()).isEmpty();
+ }
+
+ public void testSetAccountTypesWithManagementDisabledOnOrgOwnedManagedProfile()
+ throws Exception {
+ final int managedProfileUserId = 15;
+ final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+
+ addManagedProfile(admin1, managedProfileAdminUid, admin1);
+ mContext.binder.callingUid = managedProfileAdminUid;
+
+ configureProfileOwnerOfOrgOwnedDevice(admin1, managedProfileUserId);
+
+ int originalUid = mContext.binder.callingUid;
+ final String accountType = "com.example.account.type";
+ dpm.getParentProfileInstance(admin1).setAccountManagementDisabled(admin1, accountType,
+ true);
+ assertThat(dpm.getAccountTypesWithManagementDisabled()).isEmpty();
+ mContext.binder.callingUid = DpmMockContext.ANOTHER_UID;
+ assertThat(dpm.getAccountTypesWithManagementDisabled()).asList().containsExactly(
+ accountType);
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ assertThat(dpm.getAccountTypesWithManagementDisabled()).asList().containsExactly(
+ accountType);
+
+ mContext.binder.callingUid = originalUid;
+ dpm.getParentProfileInstance(admin1).setAccountManagementDisabled(admin1, accountType,
+ false);
+ mContext.binder.callingUid = DpmMockContext.ANOTHER_UID;
+ assertThat(dpm.getAccountTypesWithManagementDisabled()).isEmpty();
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ assertThat(dpm.getAccountTypesWithManagementDisabled()).isEmpty();
+ }
+
// admin1 is the outgoing DPC, adminAnotherPakcage is the incoming one.
private void assertDeviceOwnershipRevertedWithFakeTransferMetadata() throws Exception {
writeFakeTransferMetadataFile(UserHandle.USER_SYSTEM,