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

Commit b21088ef authored by Adam Bookatz's avatar Adam Bookatz
Browse files

Maximum user limits, handled consistently

Makes the various methods related to max users (what is the max, have we
reached it, how many more are allowed, actual creation procedure)
consistent between each other, and across user types, with a single
underlying source-of-truth method (getRemainingCreatableUserCount).

config_multiuserMaximumUsers now serves as the actual device max,
without making the situation exceptional for certain users. With both
flags enabled (see below), this even includes Guest users. The goal is
that OEMs can set this value to be very high (based only on storage, not
UX considerations), and to instead use config_user_types.max-allowed to
achieve fine-grained specification of maxima per user-type (based on
UX). This will allow better support for multiple profile types, without
compromising the full user experience.

Some changes:
* config_multiuserMaximumUsers - no longer has a bunch of exceptions,
  and now serves as the actual device max
* canAddMoreUsers - now is correct for all user types, including demo,
  managed profile, and private profiles
* getRemainingCreatableUserCount - simplified logic by amalgamating
  userType considerations into getMaxSupportedUsersOfType. Now takes
  into account all feature support too (including for private profiles).
  Serves as ultimate source-of-truth!
* getRemainingCreatableProfileCount - simplified and now calls
  getRemainingCreatableUserCount
* createUser - is now consistent with the other methods
* canAddPrivateProfile - no change to behaviour, but is now integrated
  into the other methods properly

Two flags are used to gate these changes:
* android.multiuser.consistentMaxUsers
  Mostly just a refactor, to make a single source-of-truth for how to
calculate user limits. There are technically some effects on Demo user
creation, since it no longer inconsistently gets a partial exemption
from the limits, but that doesn't really matter since Demo users don't
get used that way in practice.

* android.multiuser.consistentMaxUsersIncludingGuest
  Makes it so that Guest users are now subject to the overall max, just
like all other user types. This would have the effect of decreasing the
overall number of users that can be created, since Guests now count.
This should be balanced by changing the config_multiuserMaximumUsers
value, but since this requires an active change, we guard this with a
separate flag.

Note: preCreated users no longer are allowed to exceed their user type
max. This isn't particularly desired, but precreated users are
completely deprecated (see bugs 253528462 and 282987119), so there isn't
any point added to the complexity to exempt them, and one could argue
either way. No need to flag gate this.

Flag: android.multiuser.consistent_max_users
Flag: android.multiuser.consistent_max_users_including_guests
Flag: android.multiuser.max_users_in_car_is_for_secondary
Bug: 394178333
Test: atest FrameworksServicesTests:com.android.server.pm.UserManagerTest#testAddTooManyUsers_Secondary
Test: atest FrameworksServicesTests:com.android.server.pm.UserManagerTest#testAddTooManyUsers_Guest
Test: atest FrameworksServicesTests:com.android.server.pm.UserManagerTest#testAddTooManyUsers_Demo
Test: atest FrameworksServicesTests:com.android.server.pm.UserManagerTest
Test: atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceMockedTest
Change-Id: I117659792735c46c2982b4e895d6629f1a9c8fa4
parent 98139c51
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -2616,7 +2616,7 @@ package android.os {
  }

  public class UserManager {
    method @FlaggedApi("android.os.allow_private_profile") @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean canAddPrivateProfile();
    method @FlaggedApi("android.os.allow_private_profile") @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean canAddPrivateProfile();
    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createProfileForUser(@Nullable String, @NonNull String, int, int, @Nullable String[]);
    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createRestrictedProfile(@Nullable String);
    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createUser(@Nullable String, @NonNull String, int);
+21 −3
Original line number Diff line number Diff line
@@ -473,7 +473,7 @@ public class UserInfo implements Parcelable {

    /**
     * @return true if user is of type {@link UserManager#USER_TYPE_SYSTEM_HEADLESS} and
     * {@link com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser} is true.
     * {@link com.android.internal.R.bool#config_canSwitchToHeadlessSystemUser} is true.
     */
    @android.ravenwood.annotation.RavenwoodThrow
    private boolean canSwitchToHeadlessSystemUser() {
@@ -491,8 +491,10 @@ public class UserInfo implements Parcelable {
        return supportsSwitchTo();
    }

    // TODO(b/142482943): Make this logic more specific and customizable. (canHaveProfile(userType))
    /* @hide */
    /**
     * Returns whether the user can have profiles, in general (on the basis of its UserInfo data).
     * See {@link #canHaveProfile(String)} for more fine-grained determination.
     */
    public boolean canHaveProfile() {
        if (!isFull() || isProfile() || isGuest() || isRestricted() || isDemo()) {
            return false;
@@ -506,6 +508,22 @@ public class UserInfo implements Parcelable {
                                com.android.internal.R.bool.config_supportProfilesOnNonMainUser));
    }

    /**
     * Returns if the user can have a profile of the given type (on the basis of its UserInfo data).
     * @hide
     */
    public boolean canHaveProfile(String userType) {
        if (!canHaveProfile()) {
            return false;
        }
        if (UserManager.isUserTypePrivateProfile(userType)) {
            // Even if we eventually allow other users to have profiles too, only MainUsers are
            // eligible to have a Private Space, for some reason.
            return isMain();
        }
        return true;
    }

    // TODO(b/142482943): Get rid of this (after removing it from all tests) if feasible.
    /**
     * @deprecated This is dangerous since it doesn't set the mandatory fields. Use a different
+21 −0
Original line number Diff line number Diff line
@@ -31,6 +31,27 @@ flag {
    bug: "285426179"
}

flag {
    name: "consistent_max_users"
    namespace: "multiuser"
    description: "Makes max supported users be consistent and fine-grained."
    bug: "394178333"
}

flag {
    name: "consistent_max_users_including_guests"
    namespace: "multiuser"
    description: "Builds on consistent_max_users, making Guest users included in the max."
    bug: "394178333"
}

flag {
    name: "max_users_in_car_is_for_secondary"
    namespace: "multiuser"
    description: "Update Car SystemUi and Settings to specify FULL_SECONDARY for max users (alongside consistent_max_users flag)."
    bug: "394178333"
}

flag {
    name: "support_communal_profile_nextgen"
    is_exported: true
+1 −1
Original line number Diff line number Diff line
@@ -66,11 +66,11 @@ interface IUserManager {
    List<UserInfo> getProfiles(int userId, boolean enabledOnly);
    int[] getProfileIds(int userId, boolean enabledOnly);
    boolean isUserTypeEnabled(in String userType);
    int getCurrentAllowedNumberOfUsers(in String userType);
    boolean canAddMoreUsersOfType(in String userType);
    int getRemainingCreatableUserCount(in String userType);
    int getRemainingCreatableProfileCount(in String userType, int userId);
    boolean canAddMoreProfilesToUser(in String userType, int userId, boolean allowedToRemoveOne);
    boolean canAddMoreManagedProfiles(int userId, boolean allowedToRemoveOne);
    UserInfo getProfileParent(int userId);
    boolean isSameProfileGroup(int userId, int otherUserHandle);
    boolean isHeadlessSystemUserMode();
+137 −46
Original line number Diff line number Diff line
@@ -2462,10 +2462,10 @@ public class UserManager {
     */
    public static final int USER_OPERATION_ERROR_DISABLED_USER = 8;
    /**
     * Indicates user operation failed because private space is disabled on the device.
     * Indicates user operation failed because the system features do not support this user type.
     * @hide
     */
    public static final int USER_OPERATION_ERROR_PRIVATE_PROFILE = 9;
    public static final int USER_OPERATION_ERROR_FEATURE_UNSUPPORTED = 9;
    /**
     * Indicates user operation failed because user is restricted on the device.
     * @hide
@@ -2488,7 +2488,7 @@ public class UserManager {
            USER_OPERATION_ERROR_MAX_USERS,
            USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS,
            USER_OPERATION_ERROR_DISABLED_USER,
            USER_OPERATION_ERROR_PRIVATE_PROFILE,
            USER_OPERATION_ERROR_FEATURE_UNSUPPORTED,
            USER_OPERATION_ERROR_USER_RESTRICTED,
    })
    public @interface UserOperationResult {}
@@ -2648,7 +2648,9 @@ public class UserManager {
    /**
     * Returns whether this device supports multiple users with their own login and customizable
     * space.
     * @return whether the device supports multiple users.
     * <p>Note that, even if false, multiple users might still be possible, as long as no login UI
     * is required; e.g., profiles might still be supported, as they do not require a login UI.
     * @return whether the device supports multiple switchable users.
     */
    public static boolean supportsMultipleUsers() {
        return getMaxSupportedUsers() > 1
@@ -3343,7 +3345,12 @@ public class UserManager {
    }

    /**
     * Checks if it's possible to add a private profile to the context user
     * Checks if it's possible to add a private profile to the context user.
     *
     * Takes into account any possible considerations, including whether the user type is supported,
     * maximum user limits, and UserRestrictions (and so is inconsistent with similar APIs in this
     * class.)
     *
     * @return whether the context user can add a private profile.
     * @hide
     */
@@ -3351,11 +3358,17 @@ public class UserManager {
    @FlaggedApi(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE)
    @RequiresPermission(anyOf = {
            Manifest.permission.MANAGE_USERS,
            Manifest.permission.CREATE_USERS})
            Manifest.permission.CREATE_USERS,
            Manifest.permission.QUERY_USERS})
    @UserHandleAware
    public boolean canAddPrivateProfile() {
        if (!android.multiuser.Flags.enablePrivateSpaceFeatures()) return false;
        if (android.multiuser.Flags.blockPrivateSpaceCreation()) {
            // TODO(b/413464199): Ideally, move this client-side, changing it to
            // if (android.multiuser.Flags.consistentMaxUsers()) {
            //  return canAddMoreProfilesToUser(USER_TYPE_PROFILE_PRIVATE, mUserId)
            //          && !hasUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE);
            // }
            try {
                return mService.canAddPrivateProfile(mUserId);
            } catch (RemoteException re) {
@@ -5258,38 +5271,13 @@ public class UserManager {
        }
    }

    /**
     * Checks whether it's possible to add more users.
     *
     * @return true if more users can be added, false if limit has been reached.
     *
     * @deprecated use {@link #canAddMoreUsers(String)} instead.
     *
     * @hide
     */
    @Deprecated
    @RequiresPermission(anyOf = {
            android.Manifest.permission.MANAGE_USERS,
            android.Manifest.permission.CREATE_USERS
    })
    public boolean canAddMoreUsers() {
        // TODO(b/142482943): UMS has different logic, excluding Demo and Profile from counting. Why
        //                    not here? The logic is inconsistent. See UMS.canAddMoreManagedProfiles
        final List<UserInfo> users = getAliveUsers();
        final int totalUserCount = users.size();
        int aliveUserCount = 0;
        for (int i = 0; i < totalUserCount; i++) {
            UserInfo user = users.get(i);
            if (!user.isGuest()) {
                aliveUserCount++;
            }
        }
        return aliveUserCount < getMaxSupportedUsers();
    }

    /**
     * Checks whether it is possible to add more users of the given user type.
     *
     * Takes into account whether the user type is supported and maximum user limits, but does not
     * take into account UserRestrictions.
     * It is consistent with {@link #getRemainingCreatableUserCount(String)}.
     *
     * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_SECONDARY}.
     * @return true if more users of the given type can be added, otherwise false.
     * @hide
@@ -5299,12 +5287,12 @@ public class UserManager {
            android.Manifest.permission.CREATE_USERS
    })
    public boolean canAddMoreUsers(@NonNull String userType) {
        if (!android.multiuser.Flags.consistentMaxUsers()) {
            return canAddMoreUsersLegacy(userType);
        }
        try {
            if (userType.equals(USER_TYPE_FULL_GUEST)) {
            // Differs from getRemainingCreatableUserCount only because of isCreationOverrideEnabled
            return mService.canAddMoreUsersOfType(userType);
            } else {
                return canAddMoreUsers() && mService.canAddMoreUsersOfType(userType);
            }
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
@@ -5362,6 +5350,10 @@ public class UserManager {
     * if allowedToRemoveOne is true and if the user already has a managed profile, then return if
     * we could add a new managed profile to this user after removing the existing one.
     *
     * Takes into account whether the user type is supported and maximum user limits, but does not
     * take into account UserRestrictions.
     * It is consistent with {@link #getRemainingCreatableProfileCount(String)}.
     *
     * @return true if more managed profiles can be added, false if limit has been reached.
     * @hide
     */
@@ -5372,7 +5364,8 @@ public class UserManager {
    })
    public boolean canAddMoreManagedProfiles(@UserIdInt int userId, boolean allowedToRemoveOne) {
        try {
            return mService.canAddMoreManagedProfiles(userId, allowedToRemoveOne);
            return mService.canAddMoreProfilesToUser(
                    USER_TYPE_PROFILE_MANAGED, userId, allowedToRemoveOne);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
@@ -5381,6 +5374,10 @@ public class UserManager {
    /**
     * Checks whether it's possible to add more profiles of the given type to the given user.
     *
     * Takes into account whether the user type is supported and maximum user limits, but does not
     * take into account UserRestrictions.
     * It is consistent with {@link #getRemainingCreatableProfileCount(String)}.
     *
     * @param userType the type of user, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}.
     * @return true if more profiles can be added, false if limit has been reached.
     * @hide
@@ -5398,6 +5395,39 @@ public class UserManager {
        }
    }

    /**
     * Returns how many users of the given type are currently allowed on the device, including ones
     * that already exist.
     *
     * Takes into account the maximum number of users allowed on the device (of that type, and in
     * general), as well as how many users of other types are already on the device (which thereby
     * already count towards the device maximum). Does not take into account UserRestrictions.
     * It is consistent with {@link #getRemainingCreatableUserCount(String)}.
     *
     * The value will be no less than the number of users of that type that currently exist (even if
     * that value exceeds the maximum allowed, for whatever reason).
     *
     * @return a value greater than or equal to 0
     * @hide
     */
    @RequiresPermission(anyOf = {
            android.Manifest.permission.MANAGE_USERS,
            android.Manifest.permission.CREATE_USERS
    })
    public int getCurrentAllowedNumberOfUsers(@NonNull String userType) {
        if (!android.multiuser.Flags.consistentMaxUsers()) {
            throw new UnsupportedOperationException("This method requires flag consistentMaxUsers");
        }
        try {
            // Note: If we get rid of the overall device max, this should be the same as
            // UMS.getMaxSupportedUsersOfType(). But even then it can still technically differ if
            // the max gets exceeded for any reason.
            return mService.getCurrentAllowedNumberOfUsers(userType);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
    }

    /**
     * Checks whether this device supports users of the given user type.
     *
@@ -6506,15 +6536,22 @@ public class UserManager {
    }

    /**
     * Returns the maximum number of users that can be created on this device. A return value
     * of 1 means that it is a single user device.
     * @hide
     * Returns the maximum number of users (of any type) that can be created on this device.
     * A return value of 1 means that it is a single-user device.
     *
     * This value is based on the device's limitation of total number of users. This is typically
     * limited by storage considerations, not UX considerations (which depends on user type).
     * Note that this includes all types of users, including profile and guest users
     * (if {@link android.multiuser.Flags#consistentMaxUsersIncludingGuests()} is true).
     *
     * Under very rare circumstances, the number of users on the device can exceed this amount
     * (such as if it were decreased during an OTA but more users had already been created).
     *
     * @return a value greater than or equal to 1
     * @hide
     */
    @UnsupportedAppUsage
    public static int getMaxSupportedUsers() {
        // Don't allow multiple users on certain builds
        if (android.os.Build.ID.startsWith("JVP")) return 1;
        return Math.max(1, SystemProperties.getInt("fw.max_users",
                Resources.getSystem().getInteger(R.integer.config_multiuserMaximumUsers)));
    }
@@ -6831,6 +6868,60 @@ public class UserManager {
        }
    }

    /**
     * Checks whether it's possible to add more users. Legacy version of parameterless
     * {@link #canAddMoreUsers(String)}. Do not use.
     *
     * @return true if more users can be added, false if limit has been reached.
     *
     * @deprecated use {@link #canAddMoreUsers(String)} instead.
     *
     * @hide
     */
    @Deprecated
    @RequiresPermission(anyOf = {
            android.Manifest.permission.MANAGE_USERS,
            android.Manifest.permission.CREATE_USERS
    })
    public boolean canAddMoreUsersLegacy() {
        if (android.multiuser.Flags.consistentMaxUsers()
                && android.multiuser.Flags.maxUsersInCarIsForSecondary()) {
            // TODO(b/394178333): When the flags are permanent, delete this method entirely.
            throw new UnsupportedOperationException("This method is no longer supported");
        }

        // TODO(b/142482943): UMS has different logic, excluding Demo and Profile from counting.
        //                    Why not here? The logic is inconsistent. See
        //                    UMS.canAddMoreManagedProfiles
        final List<UserInfo> users = getAliveUsers();
        final int totalUserCount = users.size();
        int aliveUserCount = 0;
        for (int i = 0; i < totalUserCount; i++) {
            UserInfo user = users.get(i);
            if (!user.isGuest()) {
                aliveUserCount++;
            }
        }
        return aliveUserCount < getMaxSupportedUsers();
    }

    /** Legacy version of {@link #canAddMoreUsers(String)}. Do not use. */
    private boolean canAddMoreUsersLegacy(@NonNull String userType) {
        if (android.multiuser.Flags.consistentMaxUsers()) {
            // TODO(b/394178333): When the flag is permanent, delete this method entirely.
            throw new UnsupportedOperationException("This method is no longer supported");
        }
        try {
            if (userType.equals(USER_TYPE_FULL_GUEST)) {
                return mService.canAddMoreUsersOfType(userType);
            } else {
                return canAddMoreUsersLegacy() && mService.canAddMoreUsersOfType(userType);
            }
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
    }

    /**
     * Returns the user who should be in the foreground when boot completes.
     *
Loading