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

Commit b6665b8b authored by Brahim Chikhaoui's avatar Brahim Chikhaoui
Browse files

Add a new boot strategy for headless system user mode

The new boot strategy is to boot to the system user when the device
is provisioned, and to boot to the first switchable full user when
the device is not provisioned. This is to ensure that the system user
is always available for system services, even when the device is not
provisioned.

Context: see DD in the bug

Bug: 374926694
Change-Id: Ia0a30028be2ac9575a2dba7a01dde095cdcea092
Flag: EXEMPT new code protected by new config
Test: manually build and deploy in DUT and test both cases
Test: atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceTest
parent 6cf3567b
Loading
Loading
Loading
Loading
+38 −7
Original line number Diff line number Diff line
@@ -341,6 +341,10 @@ public class UserManagerService extends IUserManager.Stub {
    private static final String TRON_USER_CREATED = "users_user_created";
    private static final String TRON_DEMO_CREATED = "users_demo_created";

    // The boot user strategy for HSUM.
    private static final int BOOT_TO_PREVIOUS_OR_FIRST_SWITCHABLE_USER = 0;
    private static final int BOOT_TO_HSU_FOR_PROVISIONED_DEVICE = 1;

    private final Context mContext;
    private final PackageManagerService mPm;

@@ -1391,16 +1395,42 @@ public class UserManagerService extends IUserManager.Stub {
        }

        if (isHeadlessSystemUserMode()) {
            if (mContext.getResources()
                    .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser)) {
                return UserHandle.USER_SYSTEM;
            }
            final int bootStrategy = mContext.getResources()
                    .getInteger(com.android.internal.R.integer.config_hsumBootStrategy);
            switch (bootStrategy) {
                case BOOT_TO_PREVIOUS_OR_FIRST_SWITCHABLE_USER:
                    return getPreviousOrFirstSwitchableUser();
                case BOOT_TO_HSU_FOR_PROVISIONED_DEVICE:
                    return getBootUserBasedOnProvisioning();
                default:
                    Slogf.w(LOG_TAG, "Unknown HSUM boot strategy: %d", bootStrategy);
                    return getPreviousOrFirstSwitchableUser();
            }
        }
        // Not HSUM, return system user.
        return UserHandle.USER_SYSTEM;
    }

    private @UserIdInt int getBootUserBasedOnProvisioning()
            throws UserManager.CheckedUserOperationException {
        final boolean provisioned = Settings.Global.getInt(mContext.getContentResolver(),
                                            Settings.Global.DEVICE_PROVISIONED, 0) != 0;
        if (provisioned) {
            return UserHandle.USER_SYSTEM;
        } else {
            final int firstSwitchableFullUser = getFirstSwitchableUser(true);
            if (firstSwitchableFullUser != UserHandle.USER_NULL) {
                Slogf.i(LOG_TAG,
                        "Boot user is first switchable full user %d",
                                firstSwitchableFullUser);
                return firstSwitchableFullUser;
            }
            // No switchable full user found. Uh oh!
            throw new UserManager.CheckedUserOperationException(
                "No switchable full user found", USER_OPERATION_ERROR_UNKNOWN);
        }
    }

    private @UserIdInt int getPreviousOrFirstSwitchableUser()
            throws UserManager.CheckedUserOperationException {
        // Return the previous foreground user, if there is one.
@@ -1410,7 +1440,7 @@ public class UserManagerService extends IUserManager.Stub {
            return previousUser;
        }
        // No previous user. Return the first switchable user if there is one.
        final int firstSwitchableUser = getFirstSwitchableUser();
        final int firstSwitchableUser = getFirstSwitchableUser(false);
        if (firstSwitchableUser != UserHandle.USER_NULL) {
            Slogf.i(LOG_TAG,
                    "Boot user is first switchable user %d", firstSwitchableUser);
@@ -1421,12 +1451,13 @@ public class UserManagerService extends IUserManager.Stub {
            "No switchable users found", USER_OPERATION_ERROR_UNKNOWN);
    }

    private @UserIdInt int getFirstSwitchableUser() {
    private @UserIdInt int getFirstSwitchableUser(boolean fullUserOnly) {
        synchronized (mUsersLock) {
            final int userSize = mUsers.size();
            for (int i = 0; i < userSize; i++) {
                final UserData userData = mUsers.valueAt(i);
                if (userData.info.supportsSwitchToByUser()) {
                if (userData.info.supportsSwitchToByUser() &&
                        (!fullUserOnly || userData.info.isFull())) {
                    int firstSwitchable = userData.info.id;
                    return firstSwitchable;
                }
+46 −6
Original line number Diff line number Diff line
@@ -195,12 +195,12 @@ public final class UserManagerServiceTest {
        doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
        mockIsLowRamDevice(false);

        // Called when getting boot user. config_bootToHeadlessSystemUser is false by default.
        // Called when getting boot user. config_bootToHeadlessSystemUser is 0 by default.
        mSpyResources = spy(mSpiedContext.getResources());
        when(mSpiedContext.getResources()).thenReturn(mSpyResources);
        doReturn(false)
        doReturn(0)
                .when(mSpyResources)
                .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser);
                .getInteger(com.android.internal.R.integer.config_hsumBootStrategy);

        // Must construct UserManagerService in the UiThread
        mTestDir = new File(mRealContext.getDataDir(), "umstest");
@@ -859,15 +859,50 @@ public final class UserManagerServiceTest {
    }

    @Test
    public void testGetBootUser_enableBootToHeadlessSystemUser() {
    public void testGetBootUser_Headless_BootToSystemUserWhenDeviceIsProvisioned() {
        setSystemUserHeadless(true);
        doReturn(true)
        addUser(USER_ID);
        addUser(OTHER_USER_ID);
        mockProvisionedDevice(true);
        doReturn(1)
                .when(mSpyResources)
                .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser);
                .getInteger(com.android.internal.R.integer.config_hsumBootStrategy);

        assertThat(mUms.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM);
    }

    @Test
    public void testGetBootUser_Headless_BootToFirstSwitchableFullUserWhenDeviceNotProvisioned() {
        setSystemUserHeadless(true);
        addUser(USER_ID);
        addUser(OTHER_USER_ID);
        mockProvisionedDevice(false);
        doReturn(1)
                .when(mSpyResources)
                .getInteger(com.android.internal.R.integer.config_hsumBootStrategy);
        // Even if the headless system user switchable flag is true, the boot user should be the
        // first switchable full user.
        doReturn(true)
                .when(mSpyResources)
                .getBoolean(com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser);

        assertThat(mUms.getBootUser()).isEqualTo(USER_ID);
    }

    @Test
    public void testGetBootUser_Headless_ThrowsIfBootFailsNoFullUserWhenDeviceNotProvisioned()
                throws Exception {
        setSystemUserHeadless(true);
        removeNonSystemUsers();
        mockProvisionedDevice(false);
        doReturn(1)
                .when(mSpyResources)
                .getInteger(com.android.internal.R.integer.config_hsumBootStrategy);

        assertThrows(ServiceSpecificException.class,
                () -> mUms.getBootUser());
    }

    /**
     * Returns true if the user's XML file has Default restrictions
     * @param userId Id of the user.
@@ -935,6 +970,11 @@ public final class UserManagerServiceTest {
                any(), eq(android.provider.Settings.Global.USER_SWITCHER_ENABLED), anyInt()));
    }

    private void mockProvisionedDevice(boolean isProvisionedDevice) {
        doReturn(isProvisionedDevice ? 1 : 0).when(() -> Settings.Global.getInt(
                any(), eq(android.provider.Settings.Global.DEVICE_PROVISIONED), anyInt()));
    }

    private void mockIsLowRamDevice(boolean isLowRamDevice) {
        doReturn(isLowRamDevice).when(ActivityManager::isLowRamDeviceStatic);
    }