diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 2d9a99cf0d8c6..9bbc4a67a79a0 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -538,6 +538,7 @@ package android.app.admin { public class DevicePolicyManager { method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void acknowledgeNewUserDisclaimer(); + method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void calculateHasIncompatibleAccounts(); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void clearOrganizationId(); method @RequiresPermission(android.Manifest.permission.CLEAR_FREEZE_PERIOD) public void clearSystemUpdatePolicyFreezePeriodRecord(); method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceNetworkLogs(); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 4d3338beded56..a8a2ad1bb8df9 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -16824,6 +16824,23 @@ public class DevicePolicyManager { } } + /** + * Recalculate the incompatible accounts cache. + * + * @hide + */ + @TestApi + @RequiresPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + public void calculateHasIncompatibleAccounts() { + if (mService != null) { + try { + mService.calculateHasIncompatibleAccounts(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + /** * @return {@code true} if bypassing the device policy management role qualification is allowed * with the current state of the device. diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 9b0b18ac74ec9..9795caba95b89 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -608,4 +608,6 @@ interface IDevicePolicyManager { boolean isDeviceFinanced(String callerPackageName); String getFinancedDeviceKioskRoleHolder(String callerPackageName); + + void calculateHasIncompatibleAccounts(); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index d4ab7d3f25373..18fcafa84c240 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -85,6 +85,7 @@ import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY; import static android.Manifest.permission.SET_TIME; import static android.Manifest.permission.SET_TIME_ZONE; import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK; +import static android.accounts.AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_DEFAULT; @@ -532,6 +533,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @@ -1140,6 +1142,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } + + if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { + calculateHasIncompatibleAccounts(); + } + if (Intent.ACTION_BOOT_COMPLETED.equals(action) && userHandle == mOwners.getDeviceOwnerUserId()) { mBugreportCollectionManager.checkForPendingBugreportAfterBoot(); @@ -1253,6 +1260,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else if (ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)) { notifyIfManagedSubscriptionsAreUnavailable( UserHandle.of(userHandle), /* managedProfileAvailable= */ true); + } else if (LOGIN_ACCOUNTS_CHANGED_ACTION.equals(action)) { + calculateHasIncompatibleAccounts(); } } @@ -2105,6 +2114,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { filter.addAction(Intent.ACTION_USER_STOPPED); filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(Intent.ACTION_USER_UNLOCKED); + filter.addAction(LOGIN_ACCOUNTS_CHANGED_ACTION); filter.addAction(ACTION_MANAGED_PROFILE_UNAVAILABLE); filter.addAction(ACTION_MANAGED_PROFILE_AVAILABLE); filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); @@ -9560,6 +9570,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (getLockObject()) { enforceCanSetDeviceOwnerLocked(caller, admin, userId, hasIncompatibleAccountsOrNonAdb); + Preconditions.checkArgument(isPackageInstalledForUser(admin.getPackageName(), userId), "Invalid component " + admin + " for device owner"); final ActiveAdmin activeAdmin = getActiveAdminUncheckedLocked(admin, userId); @@ -18386,6 +18397,26 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return isUserAffiliatedWithDeviceLocked(userId); } + private boolean hasIncompatibleAccountsOnAnyUser() { + if (mHasIncompatibleAccounts == null) { + // Hasn't loaded for the first time yet - assume the worst + return true; + } + + for (boolean hasIncompatible : mHasIncompatibleAccounts.values()) { + if (hasIncompatible) { + return true; + } + } + + return false; + } + + private boolean hasIncompatibleAccounts(int userId) { + return mHasIncompatibleAccounts == null ? true + : mHasIncompatibleAccounts.getOrDefault(userId, /* default= */ false); + } + /** * Return true if a given user has any accounts that'll prevent installing a device or profile * owner {@code owner}. @@ -18423,7 +18454,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - boolean compatible = !hasIncompatibleAccounts(am, accounts); + boolean compatible = !hasIncompatibleAccounts(userId); if (compatible) { Slogf.w(LOG_TAG, "All accounts are compatible"); } else { @@ -18433,37 +18464,67 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { }); } - private boolean hasIncompatibleAccounts(AccountManager am, Account[] accounts) { - // TODO(b/244284408): Add test - final String[] feature_allow = - { DevicePolicyManager.ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED }; - final String[] feature_disallow = - { DevicePolicyManager.ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED }; - - for (Account account : accounts) { - if (hasAccountFeatures(am, account, feature_disallow)) { - Slogf.e(LOG_TAG, "%s has %s", account, feature_disallow[0]); - return true; - } - if (!hasAccountFeatures(am, account, feature_allow)) { - Slogf.e(LOG_TAG, "%s doesn't have %s", account, feature_allow[0]); - return true; - } - } - - return false; + @Override + public void calculateHasIncompatibleAccounts() { + new CalculateHasIncompatibleAccountsTask().executeOnExecutor( + AsyncTask.THREAD_POOL_EXECUTOR, null); } - private boolean hasAccountFeatures(AccountManager am, Account account, String[] features) { - try { - // TODO(267156507): Restore without blocking binder thread - return false; -// return am.hasFeatures(account, features, null, null).getResult(); - } catch (Exception e) { - Slogf.w(LOG_TAG, "Failed to get account feature", e); + @Nullable + private volatile Map mHasIncompatibleAccounts; + + class CalculateHasIncompatibleAccountsTask extends AsyncTask< + Void, Void, Map> { + private static final String[] FEATURE_ALLOW = + {DevicePolicyManager.ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED}; + private static final String[] FEATURE_DISALLOW = + {DevicePolicyManager.ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED}; + + @Override + protected Map doInBackground(Void... args) { + List users = mUserManagerInternal.getUsers(/* excludeDying= */ true); + Map results = new HashMap<>(); + for (UserInfo userInfo : users) { + results.put(userInfo.id, userHasIncompatibleAccounts(userInfo.id)); + } + + return results; + } + + private boolean userHasIncompatibleAccounts(int id) { + AccountManager am = mContext.createContextAsUser(UserHandle.of(id), /* flags= */ 0) + .getSystemService(AccountManager.class); + Account[] accounts = am.getAccounts(); + + for (Account account : accounts) { + if (hasAccountFeatures(am, account, FEATURE_DISALLOW)) { + return true; + } + if (!hasAccountFeatures(am, account, FEATURE_ALLOW)) { + return true; + } + } + return false; } - } + + @Override + protected void onPostExecute(Map results) { + mHasIncompatibleAccounts = Collections.unmodifiableMap(results); + + Slogf.i(LOG_TAG, "Finished calculating hasIncompatibleAccountsTask"); + } + + private static boolean hasAccountFeatures(AccountManager am, Account account, + String[] features) { + try { + return am.hasFeatures(account, features, null, null).getResult(); + } catch (Exception e) { + Slogf.w(LOG_TAG, "Failed to get account feature", e); + return false; + } + } + }; private boolean isAdb(CallerIdentity caller) { return isShellUid(caller) || isRootUid(caller); @@ -22295,26 +22356,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - private boolean hasIncompatibleAccountsOnAnyUser() { - long callingIdentity = Binder.clearCallingIdentity(); - try { - for (UserInfo user : mUserManagerInternal.getUsers(/* excludeDying= */ true)) { - AccountManager am = mContext.createContextAsUser( - UserHandle.of(user.id), /* flags= */ 0) - .getSystemService(AccountManager.class); - Account[] accounts = am.getAccounts(); - - if (hasIncompatibleAccounts(am, accounts)) { - return true; - } - } - - return false; - } finally { - Binder.restoreCallingIdentity(callingIdentity); - } - } - private void setBypassDevicePolicyManagementRoleQualificationStateInternal( String currentRoleHolder, boolean allowBypass) { boolean stateChanged = false;