diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index b8c133e869f22..688876cb3891c 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -1275,6 +1275,33 @@ public class DevicePolicyManager { */ public static final int PASSWORD_QUALITY_MANAGED = 0x80000; + /** + * @hide + * + * adb shell dpm set-{device,profile}-owner will normally not allow installing an owner to + * a user with accounts. {@link #ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED} + * and {@link #ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED} are the account features + * used by authenticator to exempt their accounts from this: + * + * + */ + public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = + "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED"; + + /** @hide See {@link #ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED} */ + public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = + "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED"; + /** * Called by an application that is administering the device to set the password restrictions it * is imposing. After setting this, the user will not be able to enter a new password that is diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index a96cef0e40bf6..3685f23b35413 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -29,6 +29,7 @@ import static org.xmlpull.v1.XmlPullParser.TEXT; import android.Manifest.permission; import android.accessibilityservice.AccessibilityServiceInfo; +import android.accounts.Account; import android.accounts.AccountManager; import android.annotation.IntDef; import android.annotation.NonNull; @@ -2943,19 +2944,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { enforceShell("forceRemoveActiveAdmin"); long ident = mInjector.binderClearCallingIdentity(); try { - final ApplicationInfo ai; - try { - ai = mIPackageManager.getApplicationInfo(adminReceiver.getPackageName(), - 0, userHandle); - } catch (RemoteException e) { - throw new IllegalStateException(e); - } - if (ai == null) { - throw new IllegalStateException("Couldn't find package to remove admin " - + adminReceiver.getPackageName() + " " + userHandle); - } - if ((ai.flags & ApplicationInfo.FLAG_TEST_ONLY) == 0) { - throw new SecurityException("Attempt to remove non-test admin " + adminReceiver + if (!isPackageTestOnly(adminReceiver.getPackageName(), userHandle)) { + throw new SecurityException("Attempt to remove non-test admin " + adminReceiver + " " + userHandle); } // If admin is a device or profile owner tidy that up first. @@ -2971,11 +2961,28 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } // Remove the admin skipping sending the broadcast. removeAdminArtifacts(adminReceiver, userHandle); + Slog.i(LOG_TAG, "Admin " + adminReceiver + " removed from user " + userHandle); } finally { mInjector.binderRestoreCallingIdentity(ident); } } + private boolean isPackageTestOnly(String packageName, int userHandle) { + final ApplicationInfo ai; + try { + ai = mIPackageManager.getApplicationInfo(packageName, + (PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE), userHandle); + } catch (RemoteException e) { + throw new IllegalStateException(e); + } + if (ai == null) { + throw new IllegalStateException("Couldn't find package to remove admin " + + packageName + " " + userHandle); + } + return (ai.flags & ApplicationInfo.FLAG_TEST_ONLY) != 0; + } + private void enforceShell(String method) { final int callingUid = Binder.getCallingUid(); if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID) { @@ -4514,7 +4521,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * not installed and therefore not available. * * @throws SecurityException if the caller is not a profile or device owner. - * @throws UnsupportedException if the package does not support being set as always-on. + * @throws UnsupportedOperationException if the package does not support being set as always-on. */ @Override public boolean setAlwaysOnVpnPackage(ComponentName admin, String vpnPackage, boolean lockdown) @@ -5710,7 +5717,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + " for device owner"); } synchronized (this) { - enforceCanSetDeviceOwnerLocked(userId); + enforceCanSetDeviceOwnerLocked(admin, userId); if (getActiveAdminUncheckedLocked(admin, userId) == null || getUserData(userId).mRemovingAdmins.contains(admin)) { throw new IllegalArgumentException("Not active admin: " + admin); @@ -5742,6 +5749,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } finally { mInjector.binderRestoreCallingIdentity(ident); } + Slog.i(LOG_TAG, "Device owner set: " + admin + " on user " + userId); return true; } } @@ -5863,6 +5871,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } finally { mInjector.binderRestoreCallingIdentity(ident); } + Slog.i(LOG_TAG, "Device owner removed: " + deviceOwnerComponent); } } @@ -5898,7 +5907,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + " not installed for userId:" + userHandle); } synchronized (this) { - enforceCanSetProfileOwnerLocked(userHandle); + enforceCanSetProfileOwnerLocked(who, userHandle); if (getActiveAdminUncheckedLocked(who, userHandle) == null || getUserData(userHandle).mRemovingAdmins.contains(who)) { @@ -5907,6 +5916,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mOwners.setProfileOwner(who, ownerName, userHandle); mOwners.writeProfileOwner(userHandle); + Slog.i(LOG_TAG, "Profile owner set: " + who + " on user " + userHandle); return true; } } @@ -5931,6 +5941,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } finally { mInjector.binderRestoreCallingIdentity(ident); } + Slog.i(LOG_TAG, "Profile owner " + who + " removed from user " + userId); } } @@ -6207,9 +6218,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * The profile owner can only be set before the user setup phase has completed, * except for: * - SYSTEM_UID - * - adb if there are not accounts. + * - adb if there are no accounts. (But see {@link #hasIncompatibleAccounts}) */ - private void enforceCanSetProfileOwnerLocked(int userHandle) { + private void enforceCanSetProfileOwnerLocked(@Nullable ComponentName owner, int userHandle) { UserInfo info = getUserInfo(userHandle); if (info == null) { // User doesn't exist. @@ -6229,8 +6240,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } int callingUid = mInjector.binderGetCallingUid(); if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) { - if (hasUserSetupCompleted(userHandle) && - AccountManager.get(mContext).getAccountsAsUser(userHandle).length > 0) { + if (hasUserSetupCompleted(userHandle) + && hasIncompatibleAccounts(userHandle, owner)) { throw new IllegalStateException("Not allowed to set the profile owner because " + "there are already some accounts on the profile"); } @@ -6247,14 +6258,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * The Device owner can only be set by adb or an app with the MANAGE_PROFILE_AND_DEVICE_OWNERS * permission. */ - private void enforceCanSetDeviceOwnerLocked(int userId) { + private void enforceCanSetDeviceOwnerLocked(@Nullable ComponentName owner, int userId) { int callingUid = mInjector.binderGetCallingUid(); boolean isAdb = callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID; if (!isAdb) { enforceCanManageProfileAndDeviceOwners(); } - final int code = checkSetDeviceOwnerPreCondition(userId, isAdb); + final int code = checkSetDeviceOwnerPreCondition(owner, userId, isAdb); switch (code) { case CODE_OK: return; @@ -8472,7 +8483,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * except for adb command if no accounts or additional users are present on the device. */ private synchronized @DeviceOwnerPreConditionCode int checkSetDeviceOwnerPreCondition( - int deviceOwnerUserId, boolean isAdb) { + @Nullable ComponentName owner, int deviceOwnerUserId, boolean isAdb) { if (mOwners.hasDeviceOwner()) { return CODE_HAS_DEVICE_OWNER; } @@ -8489,7 +8500,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (mUserManager.getUserCount() > 1) { return CODE_NONSYSTEM_USER_EXISTS; } - if (AccountManager.get(mContext).getAccounts().length > 0) { + if (hasIncompatibleAccounts(UserHandle.USER_SYSTEM, owner)) { return CODE_ACCOUNTS_NOT_EMPTY; } } else { @@ -8515,7 +8526,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private boolean isDeviceOwnerProvisioningAllowed(int deviceOwnerUserId) { - return CODE_OK == checkSetDeviceOwnerPreCondition(deviceOwnerUserId, /* isAdb */ false); + return CODE_OK == checkSetDeviceOwnerPreCondition( + /* owner unknown */ null, deviceOwnerUserId, /* isAdb */ false); } private boolean hasFeatureManagedUsers() { @@ -9048,6 +9060,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { saveSettingsLocked(userHandle); updateMaximumTimeToLockLocked(userHandle); policy.mRemovingAdmins.remove(adminReceiver); + + Slog.i(LOG_TAG, "Device admin " + adminReceiver + " removed from user " + userHandle); } // The removed admin might have disabled camera, so update user // restrictions. @@ -9072,4 +9086,81 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return policy.mDeviceProvisioningConfigApplied; } } + + /** + * Return true if a given user has any accounts that'll prevent installing a device or profile + * owner {@code owner}. + * - If the user has no accounts, then return false. + * - Otherwise, if the owner is unknown (== null), or is not test-only, then return true. + * - Otherwise, if there's any account that does not have ..._ALLOWED, or does have + * ..._DISALLOWED, return true. + * - Otherwise return false. + */ + private boolean hasIncompatibleAccounts(int userId, @Nullable ComponentName owner) { + final long token = mInjector.binderClearCallingIdentity(); + try { + final AccountManager am = AccountManager.get(mContext); + final Account accounts[] = am.getAccountsAsUser(userId); + if (accounts.length == 0) { + return false; + } + final String[] feature_allow = + { DevicePolicyManager.ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED }; + final String[] feature_disallow = + { DevicePolicyManager.ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED }; + + // Even if we find incompatible accounts along the way, we still check all accounts + // for logging. + boolean compatible = true; + for (Account account : accounts) { + if (hasAccountFeatures(am, account, feature_disallow)) { + Log.e(LOG_TAG, account + " has " + feature_disallow[0]); + compatible = false; + } + if (!hasAccountFeatures(am, account, feature_allow)) { + Log.e(LOG_TAG, account + " doesn't have " + feature_allow[0]); + compatible = false; + } + } + if (compatible) { + Log.w(LOG_TAG, "All accounts are compatible"); + } else { + Log.e(LOG_TAG, "Found incompatible accounts"); + } + + // Then check if the owner is test-only. + String log; + if (owner == null) { + // Owner is unknown. Suppose it's not test-only + compatible = false; + log = "Only test-only device/profile owner can be installed with accounts"; + } else if (isPackageTestOnly(owner.getPackageName(), userId)) { + if (compatible) { + log = "Installing test-only owner " + owner; + } else { + log = "Can't install test-only owner " + owner + " with incompatible accounts"; + } + } else { + compatible = false; + log = "Can't install non test-only owner " + owner + " with accounts"; + } + if (compatible) { + Log.w(LOG_TAG, log); + } else { + Log.e(LOG_TAG, log); + } + return !compatible; + } finally { + mInjector.binderRestoreCallingIdentity(token); + } + } + + private boolean hasAccountFeatures(AccountManager am, Account account, String[] features) { + try { + return am.hasFeatures(account, features, null, null).getResult(); + } catch (Exception e) { + Log.w(LOG_TAG, "Failed to get account feature", e); + return false; + } + } }