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

Commit a08eeb33 authored by Felipe Leme's avatar Felipe Leme
Browse files

Changed HsumBootUserInitializer to create a non-main initial user.

Currently, HBUI only creates a main user when the device is
configured to require one; otherwise, it assumes the initial user
will be created by something (like CarService).

This CL adds a 3rd option: create a non-main initial user.

Fix: 409650316
Test: atest FrameworksMockingServicesTests:HsumBootUserInitializerTest
Flag: android.multiuser.create_initial_user

Change-Id: I384fdf0e80802dc9aff598697b0d9ba17140ea15
parent 8d4fa82b
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -617,6 +617,14 @@ public abstract class UserManagerInternal {
    public abstract @UserIdInt int getBootUser(boolean waitUntilSet)
            throws UserManager.CheckedUserOperationException;

    // NOTE: used only by HsumBootUserInitializer, ideally it should be package-protected, but it's
    // located in a different package.
    /**
     * Same as {@link UserManager#setBootUser(android.os.UserHandle)}, but without checking
     * permissions.
     */
    public abstract void setBootUserId(@UserIdInt int userId);

    /**
     * Returns the user id of the communal profile, or {@link android.os.UserHandle#USER_NULL}
     * if there is no such user.
+9 −0
Original line number Diff line number Diff line
@@ -1374,6 +1374,10 @@ public class UserManagerService extends IUserManager.Stub {
    @Override
    public void setBootUser(@UserIdInt int userId) {
        checkCreateUsersPermission("Set boot user");
        setBootUserIdUnchecked(userId);
    }

    private void setBootUserIdUnchecked(@UserIdInt int userId) {
        synchronized (mUsersLock) {
            // TODO(b/263381643): Change to EventLog.
            Slogf.i(LOG_TAG, "setBootUser %d", userId);
@@ -8425,6 +8429,11 @@ public class UserManagerService extends IUserManager.Stub {
            return getBootUserUnchecked();
        }

        @Override
        public void setBootUserId(@UserIdInt int userId) {
            setBootUserIdUnchecked(userId);
        }

        @Override
        public @CanBeNULL @UserIdInt int getCommunalProfileId() {
            return getCommunalProfileIdUnchecked();
+97 −15
Original line number Diff line number Diff line
@@ -34,6 +34,8 @@ import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;

import java.util.Arrays;

/**
 * Class responsible for booting the device in the proper user on headless system user mode.
 *
@@ -42,6 +44,8 @@ final class HsumBootUserInitializer {

    private static final String TAG = HsumBootUserInitializer.class.getSimpleName();

    private static final boolean DEBUG = false;

    private final UserManagerInternal mUmi;
    private final ActivityManagerService mAms;
    private final PackageManagerService mPms;
@@ -65,29 +69,76 @@ final class HsumBootUserInitializer {
    /** Whether this device should always have a non-removable MainUser, including at first boot. */
    private final boolean mShouldAlwaysHaveMainUser;

    /** Whether it should create an initial user, but without setting it as the main user. */
    private final boolean mShouldCreateInitialUser;

    /** Static factory method for creating a {@link HsumBootUserInitializer} instance. */
    public static @Nullable HsumBootUserInitializer createInstance(ActivityManagerService am,
            PackageManagerService pms, ContentResolver contentResolver,
            boolean shouldAlwaysHaveMainUser) {
            boolean shouldAlwaysHaveMainUser, boolean shouldCreateInitialUser) {

        if (!UserManager.isHeadlessSystemUserMode()) {
            return null;
        }
        return new HsumBootUserInitializer(
                LocalServices.getService(UserManagerInternal.class),
                am, pms, contentResolver,
                shouldAlwaysHaveMainUser);
                LocalServices.getService(UserManagerInternal.class), am, pms, contentResolver,
                shouldAlwaysHaveMainUser, shouldCreateInitialUser);
    }

    @VisibleForTesting
    HsumBootUserInitializer(UserManagerInternal umi, ActivityManagerService am,
            PackageManagerService pms, ContentResolver contentResolver,
            boolean shouldAlwaysHaveMainUser) {
            boolean shouldAlwaysHaveMainUser, boolean shouldCreateInitialUser) {
        mUmi = umi;
        mAms = am;
        mPms = pms;
        mContentResolver = contentResolver;
        mShouldAlwaysHaveMainUser = shouldAlwaysHaveMainUser;
        mShouldCreateInitialUser = shouldCreateInitialUser;
        if (DEBUG) {
            Slogf.d(TAG, "HsumBootUserInitializer(): shouldAlwaysHaveMainUser=%b, "
                    + "shouldCreateInitialUser=%b",
                    shouldAlwaysHaveMainUser, shouldCreateInitialUser);
        }
    }

    // TODO(b/409650316): remove after flag's completely pushed
    private void preCreateInitialUserFlagInit(TimingsTraceAndSlog t) {
        Slogf.d(TAG, "preCreateInitialUserFlagInit())");

        if (mShouldAlwaysHaveMainUser) {
            t.traceBegin("createMainUserIfNeeded");
            preCreateInitialUserCreateMainUserIfNeeded();
            t.traceEnd();
        }
    }

    // TODO(b/409650316): remove after flag's completely pushed
    private void preCreateInitialUserCreateMainUserIfNeeded() {
        final int mainUser = mUmi.getMainUserId();
        if (mainUser != UserHandle.USER_NULL) {
            Slogf.d(TAG, "Found existing MainUser, userId=%d", mainUser);
            return;
        }

        Slogf.d(TAG, "Creating a new MainUser");
        try {
            final UserInfo newInitialUser = mUmi.createUserEvenWhenDisallowed(
                    /* name= */ null, // null will appear as "Owner" in on-demand localisation
                    UserManager.USER_TYPE_FULL_SECONDARY,
                    UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN,
                    /* disallowedPackages= */ null,
                    /* token= */ null);
            if (newInitialUser != null) {
                Slogf.i(TAG, "Successfully created MainUser, userId=%d", newInitialUser.id);
            } else {
                // Should never happen in production, but it does on HsumBootUserInitiliazerTest
                // (we could "fix" it by mocking the call, but it doesn't hurt to check anyways)
                Slogf.wtf(TAG, "createUserEvenWhenDisallowed() returned null");
            }
        } catch (UserManager.CheckedUserOperationException e) {
            Slogf.wtf(TAG, "Initial bootable MainUser creation failed", e);
        }
    }

    /**
@@ -100,11 +151,24 @@ final class HsumBootUserInitializer {
    public void init(TimingsTraceAndSlog t) {
        Slogf.i(TAG, "init())");

        if (!android.multiuser.Flags.createInitialUser()) {
            preCreateInitialUserFlagInit(t);
            return;
        }

        if (mShouldAlwaysHaveMainUser) {
            t.traceBegin("createMainUserIfNeeded");
            createMainUserIfNeeded();
            t.traceEnd();
            return;
        }
        if (mShouldCreateInitialUser) {
            t.traceBegin("createAdminUserIfNeeded");
            createAdminUserIfNeeded();
            t.traceEnd();
            return;
        }
        Slogf.d(TAG, "Not checking if initial user exists (should be handled externally)");
    }

    private void createMainUserIfNeeded() {
@@ -113,24 +177,42 @@ final class HsumBootUserInitializer {
            Slogf.d(TAG, "Found existing MainUser, userId=%d", mainUser);
            return;
        }
        createInitialUser(/* isMainUser= */ true);
    }

        Slogf.d(TAG, "Creating a new MainUser");
    private void createAdminUserIfNeeded() {
        int[] userIds = mUmi.getUserIds();
        if (userIds != null && userIds.length > 1) {
            if (DEBUG) {
                Slogf.d(TAG, "createAdminUserIfNeeded(): already have more than 1 user (%s)",
                        Arrays.toString(userIds));
            }
            return;
        }
        createInitialUser(/* isMainUser= */ false);
    }

    private void createInitialUser(boolean isMainUser) {
        String logName;
        int flags = UserInfo.FLAG_ADMIN;
        if (isMainUser) {
            flags |= UserInfo.FLAG_MAIN;
            logName = "MainUser";
        } else {
            logName = "admin user";
        }
        Slogf.d(TAG, "Creating %s", logName);
        try {
            final UserInfo newInitialUser = mUmi.createUserEvenWhenDisallowed(
                    /* name= */ null, // null will appear as "Owner" in on-demand localisation
                    UserManager.USER_TYPE_FULL_SECONDARY,
                    UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN,
                    flags,
                    /* disallowedPackages= */ null,
                    /* token= */ null);
            if (newInitialUser != null) {
                Slogf.i(TAG, "Successfully created MainUser, userId=%d", newInitialUser.id);
            } else {
                // Should never happen in production, but it does on HsumBootUserInitiliazerTest
                // (we could "fix" it by mocking the call, but it doesn't hurt to check anyways)
                Slogf.wtf(TAG, "createUserEvenWhenDisallowed() returned null");
            }
            Slogf.i(TAG, "Successfully created %s, userId=%d", logName, newInitialUser.id);
            mUmi.setBootUserId(newInitialUser.id);
        } catch (UserManager.CheckedUserOperationException e) {
            Slogf.wtf(TAG, "Initial bootable MainUser creation failed", e);
            Slogf.wtf(TAG, e, "Initial bootable %s creation failed", logName);
        }
    }

+3 −1
Original line number Diff line number Diff line
@@ -3023,7 +3023,9 @@ public final class SystemServer implements Dumpable {
        final HsumBootUserInitializer hsumBootUserInitializer =
                HsumBootUserInitializer.createInstance(
                        mActivityManagerService, mPackageManagerService, mContentResolver,
                        context.getResources().getBoolean(R.bool.config_isMainUserPermanentAdmin));
                        context.getResources().getBoolean(R.bool.config_isMainUserPermanentAdmin),
                        context.getResources().getBoolean(R.bool.config_createInitialUser)
                        );
        if (hsumBootUserInitializer != null) {
            t.traceBegin("HsumBootUserInitializer.init");
            hsumBootUserInitializer.init(t);
+189 −16
Original line number Diff line number Diff line
@@ -15,7 +15,9 @@
 */
package com.android.server;

import static android.multiuser.Flags.FLAG_CREATE_INITIAL_USER;
import static android.os.UserHandle.USER_NULL;
import static android.os.UserHandle.USER_SYSTEM;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -28,7 +30,11 @@ import android.annotation.SpecialUsers.CanBeNULL;
import android.annotation.UserIdInt;
import android.content.ContentResolver;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo.UserInfoFlag;
import android.os.UserManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.Log;

import com.android.server.am.ActivityManagerService;
@@ -39,12 +45,15 @@ import com.android.server.utils.TimingsTraceAndSlog;
import com.google.common.truth.Expect;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import java.util.Arrays;

public final class HsumBootUserInitializerTest {

    private static final String TAG = HsumBootUserInitializerTest.class.getSimpleName();
@@ -59,6 +68,10 @@ public final class HsumBootUserInitializerTest {
    @Rule
    public final MockitoRule mockito = MockitoJUnit.rule();

    @Rule
    public final SetFlagsRule setFlagsRule =
            new SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);

    @Mock
    private UserManagerInternal mMockUmi;
    @Mock
@@ -71,6 +84,13 @@ public final class HsumBootUserInitializerTest {
    @Nullable // Must be created in the same thread that it's used
    private TimingsTraceAndSlog mTracer;

    @Before
    public void setDefaultExpectations() throws Exception {
        mockGetMainUserId(USER_NULL);
        mockGetUserIds(USER_SYSTEM);
        mockCreateNewUser(NON_SYSTEM_USER_ID);
    }

    @After
    public void expectAllTracingCallsAreFinished() {
        if (mTracer == null) {
@@ -84,9 +104,56 @@ public final class HsumBootUserInitializerTest {
    }

    @Test
    public void testInit_shouldAlwaysHaveMainUserTrue_noMainUser() throws Exception {
        mockGetMainUserId(USER_NULL);
        var initializer = createHsumBootUserInitializer(true);
    @EnableFlags(FLAG_CREATE_INITIAL_USER)
    public void testInit_shouldAlwaysHaveMainUserFalse_shouldCreateInitialUserFalse_noUsers_dontCreateUser() {
        var initializer = createHsumBootUserInitializer(/* shouldAlwaysHaveMainUser= */ false,
                /* shouldCreateInitialUser= */ false);

        initializer.init(mTracer);

        expectNoUserCreated();
    }

    @Test
    @EnableFlags(FLAG_CREATE_INITIAL_USER)
    public void testInit_shouldAlwaysHaveMainUserFalse_shouldCreateInitialUserFalse_hasUser_dontCreateUser() {
        mockGetUserIds(USER_SYSTEM, NON_SYSTEM_USER_ID);
        var initializer = createHsumBootUserInitializer(/* shouldAlwaysHaveMainUser= */ false,
                /* shouldCreateInitialUser= */ false);

        initializer.init(mTracer);

        expectNoUserCreated();
    }

    @Test
    @EnableFlags(FLAG_CREATE_INITIAL_USER)
    public void testInit_shouldAlwaysHaveMainUserFalse_shouldCreateInitialUserTrue_noUser_createsUser() {
        var initializer = createHsumBootUserInitializer(/* shouldAlwaysHaveMainUser= */ false,
                /* shouldCreateInitialUser= */ true);

        initializer.init(mTracer);

        expectAdminUserCreated();
    }

    @Test
    @EnableFlags(FLAG_CREATE_INITIAL_USER)
    public void testInit_shouldAlwaysHaveMainUserFalse_shouldCreateInitialUserTrue_hasUser_dontCreateUser() {
        mockGetUserIds(USER_SYSTEM, NON_SYSTEM_USER_ID);
        var initializer = createHsumBootUserInitializer(/* shouldAlwaysHaveMainUser= */ false,
                /* shouldCreateInitialUser= */ true);

        initializer.init(mTracer);

        expectNoUserCreated();
    }

    @Test
    @EnableFlags(FLAG_CREATE_INITIAL_USER)
    public void testInit_shouldAlwaysHaveMainUserTrue_shouldCreateInitialUserFalse_noMainUser_createsMainUser() {
        var initializer = createHsumBootUserInitializer(/* shouldAlwaysHaveMainUser= */ true,
                /* shouldCreateInitialUser= */ false);

        initializer.init(mTracer);

@@ -94,9 +161,24 @@ public final class HsumBootUserInitializerTest {
    }

    @Test
    public void testInit_shouldAlwaysHaveMainUserTrue_hasMainUser() throws Exception {
    @EnableFlags(FLAG_CREATE_INITIAL_USER)
    public void testInit_shouldAlwaysHaveMainUserTrue_shouldCreateInitialUserFalse_hasNonMainUser_createsMainUser() {
        mockGetUserIds(USER_SYSTEM, NON_SYSTEM_USER_ID);
        var initializer = createHsumBootUserInitializer(/* shouldAlwaysHaveMainUser= */ true,
                /* shouldCreateInitialUser= */ false);

        initializer.init(mTracer);

        expectMainUserCreated();
    }

    @Test
    @EnableFlags(FLAG_CREATE_INITIAL_USER)
    public void testInit_shouldAlwaysHaveMainUserTrue_shouldCreateInitialUserFalse_hasMainUser_dontCreateUser() {
        mockGetMainUserId(NON_SYSTEM_USER_ID);
        var initializer = createHsumBootUserInitializer(true);
        mockGetUserIds(USER_SYSTEM, NON_SYSTEM_USER_ID);
        var initializer = createHsumBootUserInitializer(/* shouldAlwaysHaveMainUser= */ true,
                /* shouldCreateInitialUser= */ false);

        initializer.init(mTracer);

@@ -104,19 +186,60 @@ public final class HsumBootUserInitializerTest {
    }

    @Test
    public void testInit_shouldAlwaysHaveMainUserFalse_noMainUser() throws Exception {
        mockGetMainUserId(USER_NULL);
        var initializer = createHsumBootUserInitializer(/* shouldAlwaysHaveMainUser= */ false);
    @EnableFlags(FLAG_CREATE_INITIAL_USER)
    public void testInit_shouldAlwaysHaveMainUserTrue_shouldCreateInitialUserTrue_hasNonMainUser_createsMainUser() {
        mockGetUserIds(USER_SYSTEM, NON_SYSTEM_USER_ID);
        var initializer = createHsumBootUserInitializer(/* shouldAlwaysHaveMainUser= */ true,
                /* shouldCreateInitialUser= */ true);

        initializer.init(mTracer);

        expectNoUserCreated();
        expectMainUserCreated();
    }

    // TODO(b/409650316): remove tests below after flag's completely pushed

    @Test
    public void testInit_shouldAlwaysHaveMainUserFalse_hasMainUser() {
    @DisableFlags(FLAG_CREATE_INITIAL_USER)
    public void testInit_flagDisabled_shouldAlwaysHaveMainUserTrue_shouldCreateInitialUserTrue_noUser_createsMainUser() {
        var initializer = createHsumBootUserInitializer(/* shouldAlwaysHaveMainUser= */ true,
                /* shouldCreateInitialUser= */ true);

        initializer.init(mTracer);

        expectMainUserCreated();
    }

    @Test
    @DisableFlags(FLAG_CREATE_INITIAL_USER)
    public void testInit_flagDisabled_shouldAlwaysHaveMainUserTrue_shouldCreateInitialUserTrue_hasMainUser_dontCreateUser() {
        mockGetMainUserId(NON_SYSTEM_USER_ID);
        var initializer = createHsumBootUserInitializer(/* shouldAlwaysHaveMainUser= */ false);
        var initializer = createHsumBootUserInitializer(/* shouldAlwaysHaveMainUser= */ true,
                /* shouldCreateInitialUser= */ true);

        initializer.init(mTracer);

        expectNoUserCreated();
    }

    @Test
    @DisableFlags(FLAG_CREATE_INITIAL_USER)
    public void testInit_flagDisabled_shouldAlwaysHaveMainUserFalse_shouldCreateInitialUserTrue_noUser_createsAdminUser()
            throws Exception {
        var initializer = createHsumBootUserInitializer(/* shouldAlwaysHaveMainUser= */ false,
                /* shouldCreateInitialUser= */ true);

        initializer.init(mTracer);

        expectNoUserCreated();
    }

    @Test
    @DisableFlags(FLAG_CREATE_INITIAL_USER)
    public void testInit_flagDisabled_shouldAlwaysHaveMainUserFalse_shouldCreateInitialUserTrue_hasUser_dontCreateUser() {
        mockGetUserIds(USER_SYSTEM, NON_SYSTEM_USER_ID);
        var initializer = createHsumBootUserInitializer(/* shouldAlwaysHaveMainUser= */ false,
                /* shouldCreateInitialUser= */ true);

        initializer.init(mTracer);

@@ -125,19 +248,31 @@ public final class HsumBootUserInitializerTest {

    private HsumBootUserInitializer createHsumBootUserInitializer(
            boolean shouldAlwaysHaveMainUser) {
        mTracer = new TimingsTraceAndSlog(TAG);
        return createHsumBootUserInitializer(shouldAlwaysHaveMainUser,
                /* shouldCreateInitialUser= */ false);
    }

    private HsumBootUserInitializer createHsumBootUserInitializer(
            boolean shouldAlwaysHaveMainUser, boolean shouldCreateInitialUser) {
        mTracer = new TimingsTraceAndSlog(TAG);
        return new HsumBootUserInitializer(mMockUmi, mMockAms, mMockPms, mMockContentResolver,
                shouldAlwaysHaveMainUser);
                shouldAlwaysHaveMainUser, shouldCreateInitialUser);
    }

    private void expectMainUserCreated() {
        expectUserCreated(UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN);
    }

    private void expectAdminUserCreated() {
        expectUserCreated(UserInfo.FLAG_ADMIN);
    }

    private void expectUserCreated(@UserInfoFlag int flags) {
        try {
            verify(mMockUmi).createUserEvenWhenDisallowed(null,
                    UserManager.USER_TYPE_FULL_SECONDARY, UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN,
                    null, null);
                    UserManager.USER_TYPE_FULL_SECONDARY, flags, null, null);
        } catch (Exception e) {
            String msg = "didn't create main user";
            String msg = "didn't create user with flags " + flags;
            Log.e(TAG, msg, e);
            expect.withMessage(msg).fail();
        }
@@ -152,10 +287,48 @@ public final class HsumBootUserInitializerTest {
            Log.e(TAG, msg, e);
            expect.withMessage(msg).fail();
        }

        // Since the user was not created, we can automatically infer that the boot user should not
        // have been set as well
        expectSetBootUserIdNeverCalled();
    }

    private void expectSetBootUserId(@UserIdInt int userId) {
        try {
            verify(mMockUmi).setBootUserId(userId);
        } catch (Exception e) {
            String msg = "didn't call setBootUserId(" +  userId + ")";
            Log.e(TAG, msg, e);
            expect.withMessage(msg).fail();
        }
    }

    private void expectSetBootUserIdNeverCalled() {
        try {
            verify(mMockUmi, never()).setBootUserId(anyInt());
        } catch (Exception e) {
            String msg = "setBootUserId() should never be called";
            Log.e(TAG, msg, e);
            expect.withMessage(msg).fail();
        }
    }

    private void mockCreateNewUser(@UserIdInt int userId) throws Exception {
        @SuppressWarnings("deprecation")
        UserInfo userInfo = new UserInfo();
        userInfo.id = userId;
        Log.d(TAG, "createUserEvenWhenDisallowed() will return " + userInfo);
        when(mMockUmi.createUserEvenWhenDisallowed(any(), any(), anyInt(), any(), any()))
                .thenReturn(userInfo);
    }

    private void mockGetMainUserId(@CanBeNULL @UserIdInt int userId) {
        Log.d(TAG, "mockGetMainUserId(): " + userId);
        when(mMockUmi.getMainUserId()).thenReturn(userId);
    }

    private void mockGetUserIds(@UserIdInt int... userIds) {
        Log.d(TAG, "mockGetUserIds(): " + Arrays.toString(userIds));
        when(mMockUmi.getUserIds()).thenReturn(userIds);
    }
}