diff options
| author | 2021-11-22 14:24:16 +0000 | |
|---|---|---|
| committer | 2021-11-22 14:24:16 +0000 | |
| commit | c7b8b6acdde051ca53ed88ad39ee971f3a89c0fe (patch) | |
| tree | 1346633f64efc6d790d47d3a2074408a60e9db3f | |
| parent | 164fd1839b7541062c39e53593232d6e70160eec (diff) | |
| parent | d4a6b38ccd2b9dde486e7dd126f64510c0588b90 (diff) | |
Merge "Change createUser API in UserManager to create a user with seed account data."
| -rw-r--r-- | core/api/system-current.txt | 10 | ||||
| -rw-r--r-- | core/java/android/os/IUserManager.aidl | 5 | ||||
| -rw-r--r-- | core/java/android/os/NewUserRequest.java | 142 | ||||
| -rw-r--r-- | core/java/android/os/UserManager.java | 69 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/UserManagerService.java | 72 |
5 files changed, 268 insertions, 30 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 28917351801f..192b3da1dc0b 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -8681,7 +8681,11 @@ package android.os { } public final class NewUserRequest { + method @Nullable public String getAccountName(); + method @Nullable public android.os.PersistableBundle getAccountOptions(); + method @Nullable public String getAccountType(); method @Nullable public String getName(); + method @Nullable public android.graphics.Bitmap getUserIcon(); method @NonNull public String getUserType(); method public boolean isAdmin(); method public boolean isEphemeral(); @@ -8690,9 +8694,13 @@ package android.os { public static final class NewUserRequest.Builder { ctor public NewUserRequest.Builder(); method @NonNull public android.os.NewUserRequest build(); + method @NonNull public android.os.NewUserRequest.Builder setAccountName(@Nullable String); + method @NonNull public android.os.NewUserRequest.Builder setAccountOptions(@Nullable android.os.PersistableBundle); + method @NonNull public android.os.NewUserRequest.Builder setAccountType(@Nullable String); method @NonNull public android.os.NewUserRequest.Builder setAdmin(); method @NonNull public android.os.NewUserRequest.Builder setEphemeral(); method @NonNull public android.os.NewUserRequest.Builder setName(@Nullable String); + method @NonNull public android.os.NewUserRequest.Builder setUserIcon(@Nullable android.graphics.Bitmap); method @NonNull public android.os.NewUserRequest.Builder setUserType(@NonNull String); } @@ -8970,6 +8978,7 @@ package android.os { method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap) throws android.os.UserManager.UserOperationException; method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserName(@Nullable String); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean someUserHasAccount(@NonNull String, @NonNull String); field public static final String ACTION_USER_RESTRICTIONS_CHANGED = "android.os.action.USER_RESTRICTIONS_CHANGED"; field @Deprecated public static final String DISALLOW_OEM_UNLOCK = "no_oem_unlock"; field public static final String DISALLOW_RUN_IN_BACKGROUND = "no_run_in_background"; @@ -8981,6 +8990,7 @@ package android.os { field public static final int SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED = 4; // 0x4 field public static final int SWITCHABILITY_STATUS_USER_IN_CALL = 1; // 0x1 field public static final int SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED = 2; // 0x2 + field public static final int USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS = 7; // 0x7 field public static final String USER_TYPE_FULL_GUEST = "android.os.usertype.full.GUEST"; field public static final String USER_TYPE_FULL_SECONDARY = "android.os.usertype.full.SECONDARY"; field public static final String USER_TYPE_FULL_SYSTEM = "android.os.usertype.full.SYSTEM"; diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index b83970625ee4..50ca9ff3576f 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -20,6 +20,7 @@ package android.os; import android.os.Bundle; import android.os.IUserRestrictionsListener; import android.os.PersistableBundle; +import android.os.UserHandle; import android.os.UserManager; import android.content.pm.UserInfo; import android.content.IntentSender; @@ -91,6 +92,9 @@ interface IUserManager { boolean markGuestForDeletion(int userId); UserInfo findCurrentGuestUser(); boolean isQuietModeEnabled(int userId); + UserHandle createUserWithAttributes(in String userName, in String userType, int flags, + in Bitmap userIcon, + in String accountName, in String accountType, in PersistableBundle accountOptions); void setSeedAccountData(int userId, in String accountName, in String accountType, in PersistableBundle accountOptions, boolean persist); String getSeedAccountName(int userId); @@ -98,6 +102,7 @@ interface IUserManager { PersistableBundle getSeedAccountOptions(int userId); void clearSeedAccountData(int userId); boolean someUserHasSeedAccount(in String accountName, in String accountType); + boolean someUserHasAccount(in String accountName, in String accountType); boolean isProfile(int userId); boolean isManagedProfile(int userId); boolean isCloneProfile(int userId); diff --git a/core/java/android/os/NewUserRequest.java b/core/java/android/os/NewUserRequest.java index 2ebc01f2d3d0..b0e1f91c47c3 100644 --- a/core/java/android/os/NewUserRequest.java +++ b/core/java/android/os/NewUserRequest.java @@ -17,7 +17,11 @@ package android.os; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.SystemApi; +import android.content.pm.UserInfo; +import android.graphics.Bitmap; +import android.text.TextUtils; /** * Contains necessary information to create user using @@ -26,6 +30,7 @@ import android.annotation.SystemApi; * @hide */ @SystemApi +@SuppressLint("PackageLayering") public final class NewUserRequest { @Nullable private final String mName; @@ -33,16 +38,24 @@ public final class NewUserRequest { private final boolean mEphemeral; @NonNull private final String mUserType; + private final Bitmap mUserIcon; + private final String mAccountName; + private final String mAccountType; + private final PersistableBundle mAccountOptions; private NewUserRequest(Builder builder) { mName = builder.mName; mAdmin = builder.mAdmin; mEphemeral = builder.mEphemeral; mUserType = builder.mUserType; + mUserIcon = builder.mUserIcon; + mAccountName = builder.mAccountName; + mAccountType = builder.mAccountType; + mAccountOptions = builder.mAccountOptions; } /** - * Gets the user name. + * Returns the name of the user. */ @Nullable public String getName() { @@ -50,7 +63,7 @@ public final class NewUserRequest { } /** - * Is user Ephemenral? + * Returns whether the user is ephemeral. * * <p> Ephemeral user will be removed after leaving the foreground. */ @@ -59,7 +72,7 @@ public final class NewUserRequest { } /** - * Is user Admin? + * Returns whether the user is an admin. * * <p> Admin user is with administrative privileges and such user can create and * delete users. @@ -69,7 +82,17 @@ public final class NewUserRequest { } /** - * Gets user type. + * Returns the calculated flags for user creation. + */ + int getFlags() { + int flags = 0; + if (isAdmin()) flags |= UserInfo.FLAG_ADMIN; + if (isEphemeral()) flags |= UserInfo.FLAG_EPHEMERAL; + return flags; + } + + /** + * Returns the user type. * * <p> Supported types are {@link UserManager.USER_TYPE_FULL_SECONDARY} and * {@link USER_TYPE_FULL_GUEST} @@ -79,25 +102,71 @@ public final class NewUserRequest { return mUserType; } + /** + * Returns the user icon. + */ + @Nullable + public Bitmap getUserIcon() { + return mUserIcon; + } + + /** + * Returns the account name. + */ + @Nullable + public String getAccountName() { + return mAccountName; + } + + /** + * Returns the account type. + */ + @Nullable + public String getAccountType() { + return mAccountType; + } + + /** + * Returns the account options. + */ + @SuppressLint("NullableCollection") + @Nullable + public PersistableBundle getAccountOptions() { + return mAccountOptions; + } + @Override public String toString() { - return String.format( - "NewUserRequest- UserName:%s, userType:%s, IsAdmin:%s, IsEphemeral:%s.", mName, - mUserType, mAdmin, mEphemeral); + return "NewUserRequest{" + + "mName='" + mName + '\'' + + ", mAdmin=" + mAdmin + + ", mEphemeral=" + mEphemeral + + ", mUserType='" + mUserType + '\'' + + ", mAccountName='" + mAccountName + '\'' + + ", mAccountType='" + mAccountType + '\'' + + ", mAccountOptions=" + mAccountOptions + + '}'; } /** * Builder for building {@link NewUserRequest} */ + @SuppressLint("PackageLayering") public static final class Builder { private String mName; private boolean mAdmin; private boolean mEphemeral; private String mUserType = UserManager.USER_TYPE_FULL_SECONDARY; + private Bitmap mUserIcon; + private String mAccountName; + private String mAccountType; + private PersistableBundle mAccountOptions; /** * Sets user name. + * + * @return This object for method chaining. */ @NonNull public Builder setName(@Nullable String name) { @@ -110,6 +179,8 @@ public final class NewUserRequest { * * <p> Admin user is with administrative privileges and such user can create * and delete users. + * + * @return This object for method chaining. */ @NonNull public Builder setAdmin() { @@ -121,6 +192,8 @@ public final class NewUserRequest { * Sets user as ephemeral. * * <p> Ephemeral user will be removed after leaving the foreground. + * + * @return This object for method chaining. */ @NonNull public Builder setEphemeral() { @@ -134,6 +207,8 @@ public final class NewUserRequest { * Supported types are {@link UserManager.USER_TYPE_FULL_SECONDARY} and * {@link UserManager.USER_TYPE_FULL_GUEST}. Default value is * {@link UserManager.USER_TYPE_FULL_SECONDARY}. + * + * @return This object for method chaining. */ @NonNull public Builder setUserType(@NonNull String type) { @@ -142,6 +217,54 @@ public final class NewUserRequest { } /** + * Sets user icon. + * + * @return This object for method chaining. + */ + @NonNull + public Builder setUserIcon(@Nullable Bitmap userIcon) { + mUserIcon = userIcon; + return this; + } + + /** + * Sets account name that will be used by the setup wizard to initialize the user. + * + * @see android.accounts.Account + * @return This object for method chaining. + */ + @NonNull + public Builder setAccountName(@Nullable String accountName) { + mAccountName = accountName; + return this; + } + + /** + * Sets account type for the account to be created. This is required if the account name + * is not null. This will be used by the setup wizard to initialize the user. + * + * @see android.accounts.Account + * @return This object for method chaining. + */ + @NonNull + public Builder setAccountType(@Nullable String accountType) { + mAccountType = accountType; + return this; + } + + /** + * Sets account options that can contain account-specific extra information + * to be used by setup wizard to initialize the account for the user. + * + * @return This object for method chaining. + */ + @NonNull + public Builder setAccountOptions(@Nullable PersistableBundle accountOptions) { + mAccountOptions = accountOptions; + return this; + } + + /** * Builds {@link NewUserRequest} * * @throws IllegalStateException if builder is configured with incompatible properties and @@ -165,6 +288,11 @@ public final class NewUserRequest { && mUserType != UserManager.USER_TYPE_FULL_GUEST) { throw new IllegalStateException("Unsupported user type: " + mUserType); } + + if (TextUtils.isEmpty(mAccountName) != TextUtils.isEmpty(mAccountType)) { + throw new IllegalStateException( + "Account name and account type should be provided together."); + } } } } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 94375c0e949f..cf4ce9b43cf2 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -70,6 +70,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Set; /** @@ -1661,6 +1662,14 @@ public class UserManager { public static final int USER_OPERATION_ERROR_MAX_USERS = 6; /** + * Indicates user operation failed because a user with that account already exists. + * + * @hide + */ + @SystemApi + public static final int USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS = 7; + + /** * Result returned from various user operations. * * @hide @@ -1673,7 +1682,8 @@ public class UserManager { USER_OPERATION_ERROR_MAX_RUNNING_USERS, USER_OPERATION_ERROR_CURRENT_USER, USER_OPERATION_ERROR_LOW_STORAGE, - USER_OPERATION_ERROR_MAX_USERS + USER_OPERATION_ERROR_MAX_USERS, + USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS }) public @interface UserOperationResult {} @@ -3159,26 +3169,24 @@ public class UserManager { @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) public @NonNull NewUserResponse createUser(@NonNull NewUserRequest newUserRequest) { - UserInfo user = null; - int operationResult = USER_OPERATION_ERROR_UNKNOWN; try { - user = createUser(newUserRequest.getName(), newUserRequest.getUserType(), - determineFlagsForUserCreation(newUserRequest)); - } catch (UserOperationException e) { + final UserHandle userHandle = mService.createUserWithAttributes( + newUserRequest.getName(), + newUserRequest.getUserType(), + newUserRequest.getFlags(), + newUserRequest.getUserIcon(), + newUserRequest.getAccountName(), + newUserRequest.getAccountType(), + newUserRequest.getAccountOptions()); + + return new NewUserResponse(userHandle, USER_OPERATION_SUCCESS); + + } catch (ServiceSpecificException e) { Log.w(TAG, "Exception while creating user " + newUserRequest, e); - operationResult = e.getUserOperationResult(); - } - if (user == null) { - return new NewUserResponse(null, operationResult); + return new NewUserResponse(null, e.errorCode); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); } - return new NewUserResponse(user.getUserHandle(), USER_OPERATION_SUCCESS); - } - - private int determineFlagsForUserCreation(NewUserRequest newUserRequest) { - int flags = 0; - if (newUserRequest.isAdmin()) flags |= UserInfo.FLAG_ADMIN; - if (newUserRequest.isEphemeral()) flags |= UserInfo.FLAG_EPHEMERAL; - return flags; } /** @@ -4913,12 +4921,12 @@ public class UserManager { } /** - * @hide * Checks if any uninitialized user has the specific seed account name and type. * * @param accountName The account name to check for * @param accountType The account type of the account to check for * @return whether the seed account was found + * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean someUserHasSeedAccount(String accountName, String accountType) { @@ -4930,6 +4938,29 @@ public class UserManager { } /** + * Checks if any initialized or uninitialized user has the specific account name and type. + * + * @param accountName The account name to check for + * @param accountType The account type of the account to check for + * @return whether the account was found + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) + public boolean someUserHasAccount( + @NonNull String accountName, @NonNull String accountType) { + Objects.requireNonNull(accountName, "accountName must not be null"); + Objects.requireNonNull(accountType, "accountType must not be null"); + + try { + return mService.someUserHasAccount(accountName, accountType); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * @hide * User that enforces a restriction. * diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 302826ff1294..40d884598ceb 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -20,6 +20,8 @@ import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import android.Manifest; +import android.accounts.Account; +import android.accounts.AccountManager; import android.annotation.ColorRes; import android.annotation.DrawableRes; import android.annotation.NonNull; @@ -85,6 +87,7 @@ import android.provider.Settings; import android.security.GateKeeper; import android.service.gatekeeper.IGateKeeperService; import android.stats.devicepolicy.DevicePolicyEnums; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; @@ -3512,6 +3515,39 @@ public class UserManagerService extends IUserManager.Stub { } } + @Override + public UserHandle createUserWithAttributes( + String userName, String userType, @UserInfoFlag int flags, + Bitmap userIcon, + String accountName, String accountType, PersistableBundle accountOptions) { + checkManageOrCreateUsersPermission(flags); + + if (someUserHasAccountNoChecks(accountName, accountType)) { + throw new ServiceSpecificException( + UserManager.USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS); + } + + UserInfo userInfo; + try { + userInfo = createUserInternal(userName, userType, flags, + UserHandle.USER_NULL, null); + + if (userInfo == null) { + throw new ServiceSpecificException(UserManager.USER_OPERATION_ERROR_UNKNOWN); + } + } catch (UserManager.CheckedUserOperationException e) { + throw e.toServiceSpecificException(); + } + + if (userIcon != null) { + mLocalService.setUserIcon(userInfo.id, userIcon); + } + + setSeedAccountDataNoChecks(userInfo.id, accountName, accountType, accountOptions, true); + + return userInfo.getUserHandle(); + } + private UserInfo createUserInternal(@Nullable String name, @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int parentId, @Nullable String[] disallowedPackages) @@ -4934,7 +4970,12 @@ public class UserManagerService extends IUserManager.Stub { @Override public void setSeedAccountData(@UserIdInt int userId, String accountName, String accountType, PersistableBundle accountOptions, boolean persist) { - checkManageUsersPermission("Require MANAGE_USERS permission to set user seed data"); + checkManageUsersPermission("set user seed account data"); + setSeedAccountDataNoChecks(userId, accountName, accountType, accountOptions, persist); + } + + private void setSeedAccountDataNoChecks(@UserIdInt int userId, String accountName, + String accountType, PersistableBundle accountOptions, boolean persist) { synchronized (mPackagesLock) { final UserData userData; synchronized (mUsersLock) { @@ -4996,14 +5037,18 @@ public class UserManagerService extends IUserManager.Stub { } @Override - public boolean someUserHasSeedAccount(String accountName, String accountType) - throws RemoteException { - checkManageUsersPermission("Cannot check seed account information"); + public boolean someUserHasSeedAccount(String accountName, String accountType) { + checkManageUsersPermission("check seed account information"); + return someUserHasSeedAccountNoChecks(accountName, accountType); + } + + private boolean someUserHasSeedAccountNoChecks(String accountName, String accountType) { synchronized (mUsersLock) { final int userSize = mUsers.size(); for (int i = 0; i < userSize; i++) { final UserData data = mUsers.valueAt(i); if (data.info.isInitialized()) continue; + if (mRemovingUserIds.get(data.info.id)) continue; if (data.seedAccountName == null || !data.seedAccountName.equals(accountName)) { continue; } @@ -5017,6 +5062,25 @@ public class UserManagerService extends IUserManager.Stub { } @Override + public boolean someUserHasAccount(String accountName, String accountType) { + checkManageOrCreateUsersPermission("check seed account information"); + return someUserHasAccountNoChecks(accountName, accountType); + } + + private boolean someUserHasAccountNoChecks( + String accountName, String accountType) { + if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) { + return false; + } + + final Account account = new Account(accountName, accountType); + + return Binder.withCleanCallingIdentity(() -> + AccountManager.get(mContext).someUserHasAccount(account) + || someUserHasSeedAccountNoChecks(accountName, accountType)); + } + + @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) { |