/*
* 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 android.os.UserManager.USER_TYPE_FULL_SECONDARY;
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.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.any;
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.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
import android.provider.Settings;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import androidx.test.annotation.UiThreadTest;
import com.android.internal.widget.LockSettingsInternal;
import com.android.server.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.am.UserState;
import com.android.server.pm.UserManagerService.UserData;
import com.android.server.storage.DeviceStorageMonitorInternal;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
/**
* Run as {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceTest}
*/
public final class UserManagerServiceTest {
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}.
*
*
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}.
*
*
You can use {@link #addDefaultProfileAndParent()} to add both of this user to the service.
*/
private static final int PROFILE_USER_ID = 643;
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
.spyStatic(UserManager.class)
.spyStatic(LocalServices.class)
.mockStatic(Settings.Global.class)
.build();
private final Object mPackagesLock = new Object();
private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
.getTargetContext();
private final SparseArray mUsers = new SparseArray<>();
private Context mSpiedContext;
private @Mock PackageManagerService mMockPms;
private @Mock UserDataPreparer mMockUserDataPreparer;
private @Mock ActivityManagerInternal mActivityManagerInternal;
private @Mock DeviceStorageMonitorInternal mDeviceStorageMonitorInternal;
private @Mock StorageManager mStorageManager;
private @Mock LockSettingsInternal mLockSettingsInternal;
private @Mock PackageManagerInternal mPackageManagerInternal;
/**
* Reference to the {@link UserManagerService} being tested.
*/
private UserManagerService mUms;
/**
* Reference to the {@link UserManagerInternal} being tested.
*/
private UserManagerInternal mUmi;
@Before
@UiThreadTest // Needed to initialize main handler
public void setFixtures() {
mSpiedContext = spy(mRealContext);
// Called when WatchedUserStates is constructed
doNothing().when(() -> UserManager.invalidateIsUserUnlockedCache());
// Called when creating new users
when(mDeviceStorageMonitorInternal.isMemoryLow()).thenReturn(false);
mockGetLocalService(DeviceStorageMonitorInternal.class, mDeviceStorageMonitorInternal);
when(mSpiedContext.getSystemService(StorageManager.class)).thenReturn(mStorageManager);
mockGetLocalService(LockSettingsInternal.class, mLockSettingsInternal);
mockGetLocalService(PackageManagerInternal.class, mPackageManagerInternal);
doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
// 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() {
mockGetLocalService(ActivityManagerInternal.class, null);
assertWithMessage("getCurrentUserId()").that(mUms.getCurrentUserId())
.isEqualTo(UserHandle.USER_NULL);
}
@Test
public void testGetCurrentAndTargetUserIds() {
mockCurrentAndTargetUser(USER_ID, OTHER_USER_ID);
assertWithMessage("getCurrentAndTargetUserIds()")
.that(mUms.getCurrentAndTargetUserIds())
.isEqualTo(new Pair<>(USER_ID, OTHER_USER_ID));
}
@Test
public void testGetCurrentUserId() {
mockCurrentUser(USER_ID);
assertWithMessage("getCurrentUserId()").that(mUms.getCurrentUserId())
.isEqualTo(USER_ID);
}
@Test
public void testIsCurrentUserOrRunningProfileOfCurrentUser_currentUser() {
mockCurrentUser(USER_ID);
assertWithMessage("isCurrentUserOrRunningProfileOfCurrentUser(%s)", USER_ID)
.that(mUms.isCurrentUserOrRunningProfileOfCurrentUser(USER_ID)).isTrue();
}
@Test
public void testIsCurrentUserOrRunningProfileOfCurrentUser_notCurrentUser() {
mockCurrentUser(OTHER_USER_ID);
assertWithMessage("isCurrentUserOrRunningProfileOfCurrentUser(%s)", USER_ID)
.that(mUms.isCurrentUserOrRunningProfileOfCurrentUser(USER_ID)).isFalse();
}
@Test
public void testIsCurrentUserOrRunningProfileOfCurrentUser_startedProfileOfCurrentUser() {
addDefaultProfileAndParent();
startDefaultProfile();
mockCurrentUser(PARENT_USER_ID);
assertWithMessage("isCurrentUserOrRunningProfileOfCurrentUser(%s)", PROFILE_USER_ID)
.that(mUms.isCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID)).isTrue();
}
@Test
public void testIsCurrentUserOrRunningProfileOfCurrentUser_stoppedProfileOfCurrentUser() {
addDefaultProfileAndParent();
stopDefaultProfile();
mockCurrentUser(PARENT_USER_ID);
assertWithMessage("isCurrentUserOrRunningProfileOfCurrentUser(%s)", PROFILE_USER_ID)
.that(mUms.isCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID)).isFalse();
}
@Test
public void testIsCurrentUserOrRunningProfileOfCurrentUser_profileOfNonCurrentUSer() {
addDefaultProfileAndParent();
mockCurrentUser(OTHER_USER_ID);
assertWithMessage("isCurrentUserOrRunningProfileOfCurrentUser(%s)", PROFILE_USER_ID)
.that(mUms.isCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID)).isFalse();
}
@Test
public void testIsUserRunning_StartedUserShouldReturnTrue() {
addUser(USER_ID);
startUser(USER_ID);
assertWithMessage("isUserRunning(%s)", USER_ID)
.that(mUms.isUserRunning(USER_ID)).isTrue();
}
@Test
public void testIsUserRunning_StoppedUserShouldReturnFalse() {
addUser(USER_ID);
stopUser(USER_ID);
assertWithMessage("isUserRunning(%s)", USER_ID)
.that(mUms.isUserRunning(USER_ID)).isFalse();
}
@Test
public void testIsUserRunning_CurrentUserStartedWorkProfileShouldReturnTrue() {
addDefaultProfileAndParent();
startDefaultProfile();
assertWithMessage("isUserRunning(%s)", PROFILE_USER_ID)
.that(mUms.isUserRunning(PROFILE_USER_ID)).isTrue();
}
@Test
public void testIsUserRunning_CurrentUserStoppedWorkProfileShouldReturnFalse() {
addDefaultProfileAndParent();
stopDefaultProfile();
assertWithMessage("isUserRunning(%s)", PROFILE_USER_ID)
.that(mUms.isUserRunning(PROFILE_USER_ID)).isFalse();
}
@Test
public void testSetBootUser_SuppliedUserIsSwitchable() throws Exception {
addUser(USER_ID);
addUser(OTHER_USER_ID);
mUms.setBootUser(OTHER_USER_ID);
assertWithMessage("getBootUser")
.that(mUmi.getBootUser(/* waitUntilSet= */ false)).isEqualTo(OTHER_USER_ID);
}
@Test
public void testSetBootUser_NotHeadless_SuppliedUserIsNotSwitchable() throws Exception {
setSystemUserHeadless(false);
addUser(USER_ID);
addUser(OTHER_USER_ID);
addDefaultProfileAndParent();
mUms.setBootUser(PROFILE_USER_ID);
assertWithMessage("getBootUser")
.that(mUmi.getBootUser(/* waitUntilSet= */ false))
.isEqualTo(UserHandle.USER_SYSTEM);
}
@Test
public void testSetBootUser_Headless_SuppliedUserIsNotSwitchable() throws Exception {
setSystemUserHeadless(true);
addUser(USER_ID);
setLastForegroundTime(USER_ID, 1_000_000L);
addUser(OTHER_USER_ID);
setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
addDefaultProfileAndParent();
mUms.setBootUser(PROFILE_USER_ID);
// Boot user not switchable so return most recently in foreground.
assertWithMessage("getBootUser")
.that(mUmi.getBootUser(/* waitUntilSet= */ false)).isEqualTo(OTHER_USER_ID);
}
@Test
public void testGetBootUser_NotHeadless_ReturnsSystemUser() throws Exception {
setSystemUserHeadless(false);
addUser(USER_ID);
addUser(OTHER_USER_ID);
assertWithMessage("getBootUser")
.that(mUmi.getBootUser(/* waitUntilSet= */ false))
.isEqualTo(UserHandle.USER_SYSTEM);
}
@Test
public void testGetBootUser_Headless_ReturnsMostRecentlyInForeground() throws Exception {
setSystemUserHeadless(true);
addUser(USER_ID);
setLastForegroundTime(USER_ID, 1_000_000L);
addUser(OTHER_USER_ID);
setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
assertWithMessage("getBootUser")
.that(mUmi.getBootUser(/* waitUntilSet= */ false)).isEqualTo(OTHER_USER_ID);
}
@Test
public void testGetBootUser_Headless_ThrowsIfOnlySystemUserExists() throws Exception {
setSystemUserHeadless(true);
assertThrows(UserManager.CheckedUserOperationException.class,
() -> mUmi.getBootUser(/* waitUntilSet= */ false));
}
@Test
public void testGetPreviousFullUserToEnterForeground() throws Exception {
addUser(USER_ID);
setLastForegroundTime(USER_ID, 1_000_000L);
addUser(OTHER_USER_ID);
setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
assertWithMessage("getPreviousFullUserToEnterForeground")
.that(mUms.getPreviousFullUserToEnterForeground())
.isEqualTo(OTHER_USER_ID);
}
@Test
public void testGetPreviousFullUserToEnterForeground_SkipsCurrentUser() throws Exception {
addUser(USER_ID);
setLastForegroundTime(USER_ID, 1_000_000L);
addUser(OTHER_USER_ID);
setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
mockCurrentUser(OTHER_USER_ID);
assertWithMessage("getPreviousFullUserToEnterForeground should skip current user")
.that(mUms.getPreviousFullUserToEnterForeground())
.isEqualTo(USER_ID);
}
@Test
public void testGetPreviousFullUserToEnterForeground_SkipsNonFullUsers() throws Exception {
addUser(USER_ID);
setLastForegroundTime(USER_ID, 1_000_000L);
addUser(OTHER_USER_ID);
setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
mUsers.get(OTHER_USER_ID).info.flags &= ~UserInfo.FLAG_FULL;
assertWithMessage("getPreviousFullUserToEnterForeground should skip non-full users")
.that(mUms.getPreviousFullUserToEnterForeground())
.isEqualTo(USER_ID);
}
@Test
public void testGetPreviousFullUserToEnterForeground_SkipsPartialUsers() throws Exception {
addUser(USER_ID);
setLastForegroundTime(USER_ID, 1_000_000L);
addUser(OTHER_USER_ID);
setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
mUsers.get(OTHER_USER_ID).info.partial = true;
assertWithMessage("getPreviousFullUserToEnterForeground should skip partial users")
.that(mUms.getPreviousFullUserToEnterForeground())
.isEqualTo(USER_ID);
}
@Test
public void testGetPreviousFullUserToEnterForeground_SkipsDisabledUsers() throws Exception {
addUser(USER_ID);
setLastForegroundTime(USER_ID, 1_000_000L);
addUser(OTHER_USER_ID);
setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
mUsers.get(OTHER_USER_ID).info.flags |= UserInfo.FLAG_DISABLED;
assertWithMessage("getPreviousFullUserToEnterForeground should skip disabled users")
.that(mUms.getPreviousFullUserToEnterForeground())
.isEqualTo(USER_ID);
}
@Test
public void testGetPreviousFullUserToEnterForeground_SkipsRemovingUsers() throws Exception {
addUser(USER_ID);
setLastForegroundTime(USER_ID, 1_000_000L);
addUser(OTHER_USER_ID);
setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
mUms.addRemovingUserId(OTHER_USER_ID);
assertWithMessage("getPreviousFullUserToEnterForeground should skip removing users")
.that(mUms.getPreviousFullUserToEnterForeground())
.isEqualTo(USER_ID);
}
@Test
public void testCreateUserWithLongName_TruncatesName() {
UserInfo user = mUms.createUserWithThrow(generateLongString(), USER_TYPE_FULL_SECONDARY, 0);
assertThat(user.name.length()).isEqualTo(UserManager.MAX_USER_NAME_LENGTH);
UserInfo user1 = mUms.createUserWithThrow("Test", USER_TYPE_FULL_SECONDARY, 0);
assertThat(user1.name.length()).isEqualTo(4);
}
private String generateLongString() {
String partialString = "Test Name Test Name Test Name Test Name Test Name Test Name Test "
+ "Name Test Name Test Name Test Name "; //String of length 100
StringBuilder resultString = new StringBuilder();
for (int i = 0; i < 660; i++) {
resultString.append(partialString);
}
return resultString.toString();
}
private void mockCurrentUser(@UserIdInt int userId) {
mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
when(mActivityManagerInternal.getCurrentUserId()).thenReturn(userId);
}
private void mockCurrentAndTargetUser(@UserIdInt int currentUserId,
@UserIdInt int targetUserId) {
mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
when(mActivityManagerInternal.getCurrentAndTargetUserIds())
.thenReturn(new Pair<>(currentUserId, targetUserId));
}
private void mockGetLocalService(Class 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);
userData.info.flags = UserInfo.FLAG_FULL;
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 void setSystemUserHeadless(boolean headless) {
UserData systemUser = mUsers.get(UserHandle.USER_SYSTEM);
if (headless) {
systemUser.info.flags &= ~UserInfo.FLAG_FULL;
systemUser.info.userType = UserManager.USER_TYPE_SYSTEM_HEADLESS;
} else {
systemUser.info.flags |= UserInfo.FLAG_FULL;
systemUser.info.userType = UserManager.USER_TYPE_FULL_SYSTEM;
}
doReturn(headless).when(() -> UserManager.isHeadlessSystemUserMode());
}
private void setLastForegroundTime(@UserIdInt int userId, long timeMillis) {
UserData userData = mUsers.get(userId);
userData.mLastEnteredForegroundTimeMillis = timeMillis;
}
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() + "]";
}
}
}