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

Commit 7ad8fb81 authored by Felipe Leme's avatar Felipe Leme
Browse files

Minor changes for MUMD (Multiple Users on Multiple Displays)

- Updated some javadoc / comments
- Removed unnecessary test superclass
- Changed some logs

Test: atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceTest

Fixes: 239982558
Fixes: 244644281

Change-Id: Ia5e5a0e50f4b6edce5fc2c9f09cdb492b56cf13e
parent e671a18a
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -906,7 +906,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
        }
    }

    // TODO(b/239982558): might need to support --displayId as well
    // NOTE: current profiles can only be started on default display (even on automotive builds with
    // passenger displays), so there's no need to pass a display-id
    private int runProfile(PrintWriter pw) throws RemoteException {
        final PrintWriter err = getErrPrintWriter();
        String profileFile = null;
+25 −10
Original line number Diff line number Diff line
@@ -1073,9 +1073,6 @@ class UserController implements Handler.Callback {
            uss.setState(UserState.STATE_STOPPING);
            UserManagerInternal userManagerInternal = mInjector.getUserManagerInternal();
            userManagerInternal.setUserState(userId, uss.state);
            // TODO(b/239982558): for now we're just updating the user's visibility, but most likely
            // we'll need to remove this call and handle that as part of the user state workflow
            // instead.
            userManagerInternal.unassignUserFromDisplayOnStop(userId);

            updateStartedUserArrayLU();
@@ -1505,13 +1502,32 @@ class UserController implements Handler.Callback {
        return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, foreground, unlockListener);
    }

    // TODO(b/239982558): add javadoc (need to wait until the intents / SystemService callbacks are
    // defined
    /**
     * Starts a user in background and make it visible in the given display.
     *
     * <p>This call will trigger the usual "user started" lifecycle events (i.e., `SystemService`
     * callbacks and app intents), plus a call to
     * {@link UserManagerInternal.UserVisibilityListener#onUserVisibilityChanged(int, boolean)} if
     * the user visibility changed. Notice that the visibility change is independent of the user
     * workflow state, and they can mismatch in some corner events (for example, if the user was
     * already running in the background but not associated with a display, this call for that user
     * would not trigger any lifecycle event but would trigger {@code onUserVisibilityChanged}).
     *
     * <p>See {@link ActivityManager#startUserInBackgroundOnSecondaryDisplay(int, int)} for more
     * semantics.
     *
     * @param userId user to be started
     * @param displayId display where the user will be visible
     *
     * @return whether the user was started
     */
    boolean startUserOnSecondaryDisplay(@UserIdInt int userId, int displayId) {
        checkCallingHasOneOfThosePermissions("startUserOnSecondaryDisplay",
                MANAGE_USERS, INTERACT_ACROSS_USERS);

        // DEFAULT_DISPLAY is used for the current foreground user only
        // TODO(b/245939659): might need to move this check to UserVisibilityMediator to support
        // passenger-only screens
        Preconditions.checkArgument(displayId != Display.DEFAULT_DISPLAY,
                "Cannot use DEFAULT_DISPLAY");

@@ -1519,7 +1535,7 @@ class UserController implements Handler.Callback {
            return startUserNoChecks(userId, displayId, /* foreground= */ false,
                    /* unlockListener= */ null);
        } catch (RuntimeException e) {
            Slogf.w(TAG, "startUserOnSecondaryDisplay(%d, %d) failed: %s", userId, displayId, e);
            Slogf.e(TAG, "startUserOnSecondaryDisplay(%d, %d) failed: %s", userId, displayId, e);
            return false;
        }
    }
@@ -1618,7 +1634,6 @@ class UserController implements Handler.Callback {
                return false;
            }

            // TODO(b/239982558): might need something similar for bg users on secondary display
            if (foreground && isUserSwitchUiEnabled()) {
                t.traceBegin("startFreezingScreen");
                mInjector.getWindowManager().startFreezingScreen(
@@ -1674,9 +1689,9 @@ class UserController implements Handler.Callback {
                    userSwitchUiEnabled = mUserSwitchUiEnabled;
                }
                mInjector.updateUserConfiguration();
                // TODO(b/244644281): updateProfileRelatedCaches() is called on both if and else
                // parts, ideally it should be moved outside, but for now it's not as there are many
                // calls to external components here afterwards
                // NOTE: updateProfileRelatedCaches() is called on both if and else parts, ideally
                // it should be moved outside, but for now it's not as there are many calls to
                // external components here afterwards
                updateProfileRelatedCaches();
                mInjector.getWindowManager().setCurrentUser(userId);
                mInjector.reportCurWakefulnessUsageEvent();
+0 −213
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.server.pm;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;

import static com.google.common.truth.Truth.assertWithMessage;

import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.UserManager;
import android.util.Log;
import android.util.SparseArray;

import androidx.test.annotation.UiThreadTest;

import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import com.android.server.ExtendedMockitoTestCase;
import com.android.server.LocalServices;
import com.android.server.am.UserState;
import com.android.server.pm.UserManagerService.UserData;

import org.junit.After;
import org.junit.Before;
import org.mockito.Mock;

/**
 * Base class for {@link UserManagerInternalTest} and {@link UserManagerInternalTest}.
 *
 * <p>{@link UserManagerService} and its {@link UserManagerInternal} implementation have a
 * "symbiotic relationship - some methods of the former simply call the latter and vice versa.
 *
 * <p>Ideally, only one of them should have the logic, but since that's not the case, this class
 * provides the infra to make it easier to test both (which in turn would make it easier / safer to
 * refactor their logic later).
 */
// TODO(b/244644281): there is no UserManagerInternalTest anymore as the logic being tested there
// moved to UserVisibilityController, so it might be simpler to merge this class into
// UserManagerServiceTest (once the UserVisibilityController -> UserManagerService dependency is
// fixed)
abstract class UserManagerServiceOrInternalTestCase extends ExtendedMockitoTestCase {

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

    /**
     * Id for a simple user (that doesn't have profiles).
     */
    protected static final int USER_ID = 600;

    /**
     * Id for another simple user.
     */
    protected static final int OTHER_USER_ID = 666;

    /**
     * Id for a user that has one profile (whose id is {@link #PROFILE_USER_ID}.
     *
     * <p>You can use {@link #addDefaultProfileAndParent()} to add both of this user to the service.
     */
    protected static final int PARENT_USER_ID = 642;

    /**
     * Id for a profile whose parent is {@link #PARENTUSER_ID}.
     *
     * <p>You can use {@link #addDefaultProfileAndParent()} to add both of this user to the service.
     */
    protected static final int PROFILE_USER_ID = 643;

    private final Object mPackagesLock = new Object();
    private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
            .getTargetContext();
    private final SparseArray<UserData> mUsers = new SparseArray<>();

    private Context mSpiedContext;

    private @Mock PackageManagerService mMockPms;
    private @Mock UserDataPreparer mMockUserDataPreparer;
    private @Mock ActivityManagerInternal mActivityManagerInternal;

    /**
     * Reference to the {@link UserManagerService} being tested.
     */
    protected UserManagerService mUms;

    /**
     * Reference to the {@link UserManagerInternal} being tested.
     */
    protected UserManagerInternal mUmi;

    @Override
    protected void initializeSession(StaticMockitoSessionBuilder builder) {
        builder
                .spyStatic(UserManager.class)
                .spyStatic(LocalServices.class);
    }

    @Before
    @UiThreadTest // Needed to initialize main handler
    public final void setFixtures() {
        mSpiedContext = spy(mRealContext);

        // Called when WatchedUserStates is constructed
        doNothing().when(() -> UserManager.invalidateIsUserUnlockedCache());

        // Must construct UserManagerService in the UiThread
        mUms = new UserManagerService(mSpiedContext, mMockPms, mMockUserDataPreparer,
                mPackagesLock, mRealContext.getDataDir(), mUsers);
        mUmi = LocalServices.getService(UserManagerInternal.class);
        assertWithMessage("LocalServices.getService(UserManagerInternal.class)").that(mUmi)
                .isNotNull();
    }

    @After
    public final void resetUserManagerInternal() {
        // LocalServices follows the "Highlander rule" - There can be only one!
        LocalServices.removeServiceForTest(UserManagerInternal.class);
    }

    ///////////////////////////////////////////
    // Helper methods exposed to sub-classes //
    ///////////////////////////////////////////

    protected final void mockCurrentUser(@UserIdInt int userId) {
        mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);

        when(mActivityManagerInternal.getCurrentUserId()).thenReturn(userId);
    }

    protected final <T> void mockGetLocalService(Class<T> serviceClass, T service) {
        doReturn(service).when(() -> LocalServices.getService(serviceClass));
    }

    protected final void addDefaultProfileAndParent() {
        addUser(PARENT_USER_ID);
        addProfile(PROFILE_USER_ID, PARENT_USER_ID);
    }

    protected final void addProfile(@UserIdInt int profileId, @UserIdInt int parentId) {
        TestUserData profileData = new TestUserData(profileId);
        profileData.info.flags = UserInfo.FLAG_PROFILE;
        profileData.info.profileGroupId = parentId;

        addUserData(profileData);
    }

    protected final void addUser(@UserIdInt int userId) {
        TestUserData userData = new TestUserData(userId);

        addUserData(userData);
    }

    protected final void startDefaultProfile() {
        startUser(PROFILE_USER_ID);
    }

    protected final void stopDefaultProfile() {
        stopUser(PROFILE_USER_ID);
    }

    protected final void startUser(@UserIdInt int userId) {
        setUserState(userId, UserState.STATE_RUNNING_UNLOCKED);
    }

    protected final void stopUser(@UserIdInt int userId) {
        setUserState(userId, UserState.STATE_STOPPING);
    }

    protected final void setUserState(@UserIdInt int userId, int userState) {
        mUmi.setUserState(userId, userState);
    }

    ///////////////////
    // Private infra //
    ///////////////////

    private void addUserData(TestUserData userData) {
        Log.d(TAG, "Adding " + userData);
        mUsers.put(userData.info.id, userData);
    }

    private static final class TestUserData extends UserData {

        @SuppressWarnings("deprecation")
        TestUserData(@UserIdInt int userId) {
            info = new UserInfo();
            info.id = userId;
        }

        @Override
        public String toString() {
            return "TestUserData[" + info.toFullString() + "]";
        }
    }
}
+168 −1
Original line number Diff line number Diff line
@@ -15,17 +15,116 @@
 */
package com.android.server.pm;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;

import static com.google.common.truth.Truth.assertWithMessage;

import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import android.util.SparseArray;

import androidx.test.annotation.UiThreadTest;

import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import com.android.server.ExtendedMockitoTestCase;
import com.android.server.LocalServices;
import com.android.server.am.UserState;
import com.android.server.pm.UserManagerService.UserData;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;

/**
 * Run as {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceTest}
 */
public final class UserManagerServiceTest extends UserManagerServiceOrInternalTestCase {
public final class UserManagerServiceTest extends ExtendedMockitoTestCase {

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

    /**
     * Id for a simple user (that doesn't have profiles).
     */
    private static final int USER_ID = 600;

    /**
     * Id for another simple user.
     */
    private static final int OTHER_USER_ID = 666;

    /**
     * Id for a user that has one profile (whose id is {@link #PROFILE_USER_ID}.
     *
     * <p>You can use {@link #addDefaultProfileAndParent()} to add both of this user to the service.
     */
    private static final int PARENT_USER_ID = 642;

    /**
     * Id for a profile whose parent is {@link #PARENTUSER_ID}.
     *
     * <p>You can use {@link #addDefaultProfileAndParent()} to add both of this user to the service.
     */
    private static final int PROFILE_USER_ID = 643;

    private final Object mPackagesLock = new Object();
    private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
            .getTargetContext();
    private final SparseArray<UserData> mUsers = new SparseArray<>();

    private Context mSpiedContext;

    private @Mock PackageManagerService mMockPms;
    private @Mock UserDataPreparer mMockUserDataPreparer;
    private @Mock ActivityManagerInternal mActivityManagerInternal;

    /**
     * Reference to the {@link UserManagerService} being tested.
     */
    private UserManagerService mUms;

    /**
     * Reference to the {@link UserManagerInternal} being tested.
     */
    private UserManagerInternal mUmi;

    @Override
    protected void initializeSession(StaticMockitoSessionBuilder builder) {
        builder
                .spyStatic(UserManager.class)
                .spyStatic(LocalServices.class);
    }

    @Before
    @UiThreadTest // Needed to initialize main handler
    public void setFixtures() {
        mSpiedContext = spy(mRealContext);

        // Called when WatchedUserStates is constructed
        doNothing().when(() -> UserManager.invalidateIsUserUnlockedCache());

        // Must construct UserManagerService in the UiThread
        mUms = new UserManagerService(mSpiedContext, mMockPms, mMockUserDataPreparer,
                mPackagesLock, mRealContext.getDataDir(), mUsers);
        mUmi = LocalServices.getService(UserManagerInternal.class);
        assertWithMessage("LocalServices.getService(UserManagerInternal.class)").that(mUmi)
                .isNotNull();
    }

    @After
    public void resetUserManagerInternal() {
        // LocalServices follows the "Highlander rule" - There can be only one!
        LocalServices.removeServiceForTest(UserManagerInternal.class);
    }

    @Test
    public void testGetCurrentUserId_amInternalNotReady() {
@@ -123,4 +222,72 @@ public final class UserManagerServiceTest extends UserManagerServiceOrInternalTe
        assertWithMessage("isUserRunning(%s)", PROFILE_USER_ID)
                .that(mUms.isUserRunning(PROFILE_USER_ID)).isFalse();
    }

    private void mockCurrentUser(@UserIdInt int userId) {
        mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);

        when(mActivityManagerInternal.getCurrentUserId()).thenReturn(userId);
    }

    private <T> void mockGetLocalService(Class<T> serviceClass, T service) {
        doReturn(service).when(() -> LocalServices.getService(serviceClass));
    }

    private void addDefaultProfileAndParent() {
        addUser(PARENT_USER_ID);
        addProfile(PROFILE_USER_ID, PARENT_USER_ID);
    }

    private void addProfile(@UserIdInt int profileId, @UserIdInt int parentId) {
        TestUserData profileData = new TestUserData(profileId);
        profileData.info.flags = UserInfo.FLAG_PROFILE;
        profileData.info.profileGroupId = parentId;

        addUserData(profileData);
    }

    private void addUser(@UserIdInt int userId) {
        TestUserData userData = new TestUserData(userId);

        addUserData(userData);
    }

    private void startDefaultProfile() {
        startUser(PROFILE_USER_ID);
    }

    private void stopDefaultProfile() {
        stopUser(PROFILE_USER_ID);
    }

    private void startUser(@UserIdInt int userId) {
        setUserState(userId, UserState.STATE_RUNNING_UNLOCKED);
    }

    private void stopUser(@UserIdInt int userId) {
        setUserState(userId, UserState.STATE_STOPPING);
    }

    private void setUserState(@UserIdInt int userId, int userState) {
        mUmi.setUserState(userId, userState);
    }

    private void addUserData(TestUserData userData) {
        Log.d(TAG, "Adding " + userData);
        mUsers.put(userData.info.id, userData);
    }

    private static final class TestUserData extends UserData {

        @SuppressWarnings("deprecation")
        TestUserData(@UserIdInt int userId) {
            info = new UserInfo();
            info.id = userId;
        }

        @Override
        public String toString() {
            return "TestUserData[" + info.toFullString() + "]";
        }
    }
}
+0 −1
Original line number Diff line number Diff line
@@ -251,7 +251,6 @@ public class UserControllerTest {
                .isTrue();
        verifyUserAssignedToDisplay(TEST_USER_ID, 42);

        // TODO(b/239982558): might need to change assertions
        verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
        verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
        verify(mInjector, never()).clearAllLockedTasks(anyString());