summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Yasin Kilicdere <tyk@google.com> 2021-11-17 11:12:22 +0000
committer Yasin Kilicdere <tyk@google.com> 2021-11-19 19:17:26 +0000
commitd4a6b38ccd2b9dde486e7dd126f64510c0588b90 (patch)
treee7a48e4d704c69c47dbd19b45b1e6bb0712131c7
parent04f9d971cca85c520e836bcecfdededa25227ef8 (diff)
Change createUser API in UserManager to create a user with seed account data.
Add createUserWithAttributes method to UserManagerService. Refactor createUser API in UserManager to call createUserWithAttributes in UserManagerService. Add someUserHasSeedAccountOrAccount method to UserManager system API. Did some refactoring on UserManagerService to prevent permission check chaining. Bug: 204560945 Bug: 205100630 Test: atest UserManagerTest (cts) Change-Id: I137ddcddba2ad422f74231fe37d3d2469b4562fc
-rw-r--r--core/api/system-current.txt10
-rw-r--r--core/java/android/os/IUserManager.aidl5
-rw-r--r--core/java/android/os/NewUserRequest.java142
-rw-r--r--core/java/android/os/UserManager.java69
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java72
5 files changed, 268 insertions, 30 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a6f92760ccef..9fd7158410ef 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -8678,7 +8678,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();
@@ -8687,9 +8691,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);
}
@@ -8967,6 +8975,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";
@@ -8978,6 +8987,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) {