Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d4a6b38c authored by Yasin Kilicdere's avatar Yasin Kilicdere
Browse files

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
parent 04f9d971
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -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";
+5 −0
Original line number Diff line number Diff line
@@ -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);
+135 −7
Original line number Diff line number Diff line
@@ -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) {
@@ -141,6 +216,54 @@ public final class NewUserRequest {
            return this;
        }

        /**
         * 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}
         *
@@ -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.");
            }
        }
    }
}
+50 −19
Original line number Diff line number Diff line
@@ -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;

/**
@@ -1660,6 +1661,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.
     *
@@ -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(user.getUserHandle(), USER_OPERATION_SUCCESS);
            return new NewUserResponse(null, e.errorCode);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }

    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) {
@@ -4929,6 +4937,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.
+68 −4
Original line number Diff line number Diff line
@@ -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;
                }
@@ -5016,6 +5061,25 @@ public class UserManagerService extends IUserManager.Stub {
        return false;
    }

    @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,