UserVisibilityMediator refactoring, step 2.

The previous refactoring introduced a startUser() on
UserVisibilityMediator and changed UserManagerInternal to call both
startUser() and assignUserToDisplay(); this CL "merges" these two
methods into just startUser(), and changed UserController.startUser()
workflow so the logic of whether the user is visible is handled by
UserVisibilityMediator.

Test: atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceTest UserVisibilityMediatorMUMDTest UserVisibilityMediatorSUSDTest UserControllerTest # unit
Test: atest CtsMultiUserTestCases:android.multiuser.cts.MultipleUsersOnMultipleDisplaysTest # CTS
Test: adb shell dumpsys user --visibility-mediator

Bug: 244644281

Change-Id: Ide7b62c1ab3bb119dde7dc2790919ad3c864b3e9
This commit is contained in:
Felipe Leme
2022-10-28 11:06:49 -07:00
parent 90e81227d9
commit d16bc3eea3
8 changed files with 314 additions and 201 deletions

View File

@@ -1090,7 +1090,7 @@ class UserController implements Handler.Callback {
// 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.unassignUserFromDisplay(userId);
userManagerInternal.unassignUserFromDisplayOnStop(userId);
final boolean visibilityChanged;
boolean visibleBefore;
@@ -1650,13 +1650,30 @@ class UserController implements Handler.Callback {
return false;
}
if (!userInfo.preCreated) {
// TODO(b/244644281): UMI should return whether the user is visible. And if fails,
// the user should not be in the mediator's started users structure
mInjector.getUserManagerInternal().assignUserToDisplay(userId,
userInfo.profileGroupId, foreground, displayId);
t.traceBegin("assignUserToDisplayOnStart");
int result = mInjector.getUserManagerInternal().assignUserToDisplayOnStart(userId,
userInfo.profileGroupId, foreground, displayId);
t.traceEnd();
boolean visible;
switch (result) {
case UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE:
visible = true;
break;
case UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE:
visible = false;
break;
default:
Slogf.wtf(TAG, "Wrong result from assignUserToDisplayOnStart(): %d", result);
// Fall through
case UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE:
Slogf.e(TAG, "%s user(%d) / display (%d) assignment failed: %s",
(foreground ? "fg" : "bg"), userId, displayId,
UserManagerInternal.userAssignmentResultToString(result));
return false;
}
// TODO(b/239982558): might need something similar for bg users on secondary display
if (foreground && isUserSwitchUiEnabled()) {
t.traceBegin("startFreezingScreen");
@@ -1751,19 +1768,6 @@ class UserController implements Handler.Callback {
}
t.traceEnd();
// Need to call UM when user is on background, as there are some cases where the user
// cannot be started in background on a secondary display (for example, if user is a
// profile).
// TODO(b/253103846): it's also explicitly checking if the user is the USER_SYSTEM, as
// the UM call would return true during boot (when CarService / BootUserInitializer
// calls AM.startUserInBackground() because the system user is still the current user.
// TODO(b/244644281): another fragility of this check is that it must wait to call
// UMI.isUserVisible() until the user state is check, as that method checks if the
// profile of the current user is started. We should fix that dependency so the logic
// belongs to just one place (like UserDisplayAssigner)
boolean visible = foreground
|| userId != UserHandle.USER_SYSTEM
&& mInjector.getUserManagerInternal().isUserVisible(userId);
if (visible) {
synchronized (mLock) {
addVisibleUserLocked(userId);
@@ -1816,8 +1820,8 @@ class UserController implements Handler.Callback {
// user that was started in the background before), so it's necessary to explicitly
// notify the services (while when the user starts from BOOTING, USER_START_MSG
// takes care of that.
mHandler.sendMessage(mHandler.obtainMessage(USER_VISIBILITY_CHANGED_MSG, userId,
visible ? 1 : 0));
mHandler.sendMessage(
mHandler.obtainMessage(USER_VISIBILITY_CHANGED_MSG, userId, 1));
}
t.traceBegin("sendMessages");

View File

@@ -25,6 +25,7 @@ import android.content.pm.UserProperties;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.UserManager;
import android.util.DebugUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -46,6 +47,18 @@ public abstract class UserManagerInternal {
public @interface OwnerType {
}
public static final int USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE = 1;
public static final int USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE = 2;
public static final int USER_ASSIGNMENT_RESULT_FAILURE = -1;
private static final String PREFIX_USER_ASSIGNMENT_RESULT = "USER_ASSIGNMENT_RESULT";
@IntDef(flag = false, prefix = {PREFIX_USER_ASSIGNMENT_RESULT}, value = {
USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE,
USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE,
USER_ASSIGNMENT_RESULT_FAILURE
})
public @interface UserAssignmentResult {}
public interface UserRestrictionsListener {
/**
* Called when a user restriction changes.
@@ -343,34 +356,28 @@ public abstract class UserManagerInternal {
public abstract @Nullable UserProperties getUserProperties(@UserIdInt int userId);
/**
* Assigns a user to a display.
*
* <p>On most devices this call will be a no-op, but it will be used on devices that support
* multiple users on multiple displays (like automotives with passenger displays).
* Assigns a user to a display when it's starting, returning whether the assignment succeeded
* and the user is {@link UserManager#isUserVisible() visible}.
*
* <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} (when a user
* is started)
* is started). If other clients (like {@code CarService} need to explicitly change the user /
* display assignment, we'll need to provide other APIs.
*
* <p><b>NOTE: </b>this method doesn't validate if the display exists, it's up to the caller to
* check it. In fact, one of the intended clients for this method is
* {@code DisplayManagerService}, which will call it when a virtual display is created (another
* client is {@code UserController}, which will call it when a user is started).
* pass a valid display id.
*/
// TODO(b/244644281): rename to assignUserToDisplayOnStart() and make sure it's called on boot
// as well
public abstract void assignUserToDisplay(@UserIdInt int userId, @UserIdInt int profileGroupId,
public abstract @UserAssignmentResult int assignUserToDisplayOnStart(@UserIdInt int userId,
@UserIdInt int profileGroupId,
boolean foreground, int displayId);
/**
* Unassigns a user from its current display.
*
* <p>On most devices this call will be a no-op, but it will be used on devices that support
* multiple users on multiple displays (like automotives with passenger displays).
* Unassigns a user from its current display when it's stopping.
*
* <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} (when a user
* is stopped).
* is stopped). If other clients (like {@code CarService} need to explicitly change the user /
* display assignment, we'll need to provide other APIs.
*/
public abstract void unassignUserFromDisplay(@UserIdInt int userId);
public abstract void unassignUserFromDisplayOnStop(@UserIdInt int userId);
/**
* Returns {@code true} if the user is visible (as defined by
@@ -413,6 +420,15 @@ public abstract class UserManagerInternal {
*/
public abstract @UserIdInt int getUserAssignedToDisplay(int displayId);
/**
* Gets the user-friendly representation of the {@code result} of a
* {@link #assignUserToDisplayOnStart(int, int, boolean, int)} call.
*/
public static String userAssignmentResultToString(@UserAssignmentResult int result) {
return DebugUtils.constantToString(UserManagerInternal.class, PREFIX_USER_ASSIGNMENT_RESULT,
result);
}
/** Adds a {@link UserVisibilityListener}. */
public abstract void addUserVisibilityListener(UserVisibilityListener listener);

View File

@@ -6799,15 +6799,14 @@ public class UserManagerService extends IUserManager.Stub {
}
@Override
public void assignUserToDisplay(@UserIdInt int userId, @UserIdInt int profileGroupId,
public int assignUserToDisplayOnStart(@UserIdInt int userId, @UserIdInt int profileGroupId,
boolean foreground, int displayId) {
mUserVisibilityMediator.startUser(userId, profileGroupId, foreground, displayId);
mUserVisibilityMediator.assignUserToDisplay(userId, profileGroupId, displayId);
return mUserVisibilityMediator.startUser(userId, profileGroupId, foreground,
displayId);
}
@Override
public void unassignUserFromDisplay(@UserIdInt int userId) {
mUserVisibilityMediator.unassignUserFromDisplay(userId);
public void unassignUserFromDisplayOnStop(@UserIdInt int userId) {
mUserVisibilityMediator.stopUser(userId);
}

View File

@@ -20,12 +20,15 @@ import static android.os.UserHandle.USER_NULL;
import static android.os.UserHandle.USER_SYSTEM;
import static android.view.Display.DEFAULT_DISPLAY;
import android.annotation.IntDef;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.DebugUtils;
import android.util.Dumpable;
import android.util.IndentingPrintWriter;
import android.util.SparseIntArray;
@@ -34,6 +37,7 @@ import android.view.Display;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
import com.android.server.utils.Slogf;
import java.io.PrintWriter;
@@ -52,23 +56,10 @@ public final class UserVisibilityMediator implements Dumpable {
private static final String TAG = UserVisibilityMediator.class.getSimpleName();
private static final String PREFIX_START_USER_RESULT = "START_USER_";
// TODO(b/242195409): might need to change this if boot logic is refactored for HSUM devices
@VisibleForTesting
static final int INITIAL_CURRENT_USER_ID = USER_SYSTEM;
public static final int START_USER_RESULT_SUCCESS_VISIBLE = 1;
public static final int START_USER_RESULT_SUCCESS_INVISIBLE = 2;
public static final int START_USER_RESULT_FAILURE = -1;
@IntDef(flag = false, prefix = {PREFIX_START_USER_RESULT}, value = {
START_USER_RESULT_SUCCESS_VISIBLE,
START_USER_RESULT_SUCCESS_INVISIBLE,
START_USER_RESULT_FAILURE
})
public @interface StartUserResult {}
private final Object mLock = new Object();
private final boolean mUsersOnSecondaryDisplaysEnabled;
@@ -97,10 +88,37 @@ public final class UserVisibilityMediator implements Dumpable {
}
/**
* TODO(b/244644281): merge with assignUserToDisplay() or add javadoc.
* See {@link UserManagerInternal#assignUserToDisplayOnStart(int, int, boolean, int)}.
*/
public @StartUserResult int startUser(@UserIdInt int userId, @UserIdInt int profileGroupId,
public @UserAssignmentResult int startUser(@UserIdInt int userId, @UserIdInt int profileGroupId,
boolean foreground, int displayId) {
// TODO(b/244644281): this method need to perform 4 actions:
//
// 1. Check if the user can be started given the provided arguments
// 2. If it can, decide whether it's visible or not (which is the return value)
// 3. Update the current user / profiles state
// 4. Update the users on secondary display state (if applicable)
//
// Ideally, they should be done "atomically" (i.e, only changing state while holding the
// mLock), but the initial implementation is just calling the existing methods, as the
// focus is to change the UserController startUser() workflow (so it relies on this class
// for the logic above).
//
// The next CL will refactor it (and the unit tests) to achieve that atomicity.
int result = startOnly(userId, profileGroupId, foreground, displayId);
if (result != USER_ASSIGNMENT_RESULT_FAILURE) {
assignUserToDisplay(userId, profileGroupId, displayId);
}
return result;
}
/**
* @deprecated - see comment inside {@link #startUser(int, int, boolean, int)}
*/
@Deprecated
@VisibleForTesting
@UserAssignmentResult int startOnly(@UserIdInt int userId,
@UserIdInt int profileGroupId, boolean foreground, int displayId) {
int actualProfileGroupId = profileGroupId == NO_PROFILE_GROUP_ID
? userId
: profileGroupId;
@@ -111,7 +129,7 @@ public final class UserVisibilityMediator implements Dumpable {
if (foreground && displayId != DEFAULT_DISPLAY) {
Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start foreground user on "
+ "secondary display", userId, actualProfileGroupId, foreground, displayId);
return START_USER_RESULT_FAILURE;
return USER_ASSIGNMENT_RESULT_FAILURE;
}
int visibility;
@@ -121,13 +139,12 @@ public final class UserVisibilityMediator implements Dumpable {
Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start profile user on "
+ "secondary display", userId, actualProfileGroupId, foreground,
displayId);
return START_USER_RESULT_FAILURE;
return USER_ASSIGNMENT_RESULT_FAILURE;
}
if (foreground) {
Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start profile user in "
+ "foreground", userId, actualProfileGroupId, foreground,
displayId);
return START_USER_RESULT_FAILURE;
+ "foreground", userId, actualProfileGroupId, foreground, displayId);
return USER_ASSIGNMENT_RESULT_FAILURE;
} else {
boolean isParentRunning = mStartedProfileGroupIds
.get(actualProfileGroupId) == actualProfileGroupId;
@@ -135,18 +152,18 @@ public final class UserVisibilityMediator implements Dumpable {
Slogf.d(TAG, "profile parent running: %b", isParentRunning);
}
visibility = isParentRunning
? START_USER_RESULT_SUCCESS_VISIBLE
: START_USER_RESULT_SUCCESS_INVISIBLE;
? USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE
: USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
}
} else if (foreground) {
mCurrentUserId = userId;
visibility = START_USER_RESULT_SUCCESS_VISIBLE;
visibility = USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
} else {
visibility = START_USER_RESULT_SUCCESS_INVISIBLE;
visibility = USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
}
if (DBG) {
Slogf.d(TAG, "adding user / profile mapping (%d -> %d) and returning %s",
userId, actualProfileGroupId, startUserResultToString(visibility));
userId, actualProfileGroupId, userAssignmentResultToString(visibility));
}
mStartedProfileGroupIds.put(userId, actualProfileGroupId);
}
@@ -154,21 +171,11 @@ public final class UserVisibilityMediator implements Dumpable {
}
/**
* TODO(b/244644281): merge with unassignUserFromDisplay() or add javadoc (and unit tests)
* @deprecated - see comment inside {@link #startUser(int, int, boolean, int)}
*/
public void stopUser(@UserIdInt int userId) {
if (DBG) {
Slogf.d(TAG, "stopUser(%d)", userId);
}
synchronized (mLock) {
mStartedProfileGroupIds.delete(userId);
}
}
/**
* See {@link UserManagerInternal#assignUserToDisplay(int, int)}.
*/
public void assignUserToDisplay(int userId, int profileGroupId, int displayId) {
@Deprecated
@VisibleForTesting
void assignUserToDisplay(int userId, int profileGroupId, int displayId) {
if (DBG) {
Slogf.d(TAG, "assignUserToDisplay(%d, %d): mUsersOnSecondaryDisplaysEnabled=%b",
userId, displayId, mUsersOnSecondaryDisplaysEnabled);
@@ -246,22 +253,25 @@ public final class UserVisibilityMediator implements Dumpable {
}
/**
* See {@link UserManagerInternal#unassignUserFromDisplay(int)}.
* See {@link UserManagerInternal#unassignUserFromDisplayOnStop(int)}.
*/
public void unassignUserFromDisplay(int userId) {
public void stopUser(int userId) {
if (DBG) {
Slogf.d(TAG, "unassignUserFromDisplay(%d)", userId);
Slogf.d(TAG, "stopUser(%d)", userId);
}
if (!mUsersOnSecondaryDisplaysEnabled) {
// Don't need to do anything because methods (such as isUserVisible()) already know
// that the current user (and their profiles) is assigned to the default display.
if (DBG) {
Slogf.d(TAG, "ignoring when device doesn't support MUMD");
}
return;
}
synchronized (mLock) {
if (DBG) {
Slogf.d(TAG, "Removing %d from mStartedProfileGroupIds (%s)", userId,
mStartedProfileGroupIds);
}
mStartedProfileGroupIds.delete(userId);
if (!mUsersOnSecondaryDisplaysEnabled) {
// Don't need to do update mUsersOnSecondaryDisplays because methods (such as
// isUserVisible()) already know that the current user (and their profiles) is
// assigned to the default display.
return;
}
if (DBG) {
Slogf.d(TAG, "Removing %d from mUsersOnSecondaryDisplays (%s)", userId,
mUsersOnSecondaryDisplays);
@@ -395,33 +405,40 @@ public final class UserVisibilityMediator implements Dumpable {
ipw.print("Current user id: ");
ipw.println(mCurrentUserId);
ipw.print("Number of started user / profile group mappings: ");
ipw.println(mStartedProfileGroupIds.size());
if (mStartedProfileGroupIds.size() > 0) {
ipw.increaseIndent();
for (int i = 0; i < mStartedProfileGroupIds.size(); i++) {
ipw.print("User #");
ipw.print(mStartedProfileGroupIds.keyAt(i));
ipw.print(" -> profile #");
ipw.println(mStartedProfileGroupIds.valueAt(i));
}
ipw.decreaseIndent();
}
dumpIntArray(ipw, mStartedProfileGroupIds, "started user / profile group", "u", "pg");
ipw.print("Supports users on secondary displays: ");
ipw.print("Supports background users on secondary displays: ");
ipw.println(mUsersOnSecondaryDisplaysEnabled);
if (mUsersOnSecondaryDisplaysEnabled) {
ipw.print("Users on secondary displays: ");
synchronized (mLock) {
ipw.println(mUsersOnSecondaryDisplays);
}
dumpIntArray(ipw, mUsersOnSecondaryDisplays, "background user / secondary display",
"u", "d");
}
}
ipw.decreaseIndent();
}
private static void dumpIntArray(IndentingPrintWriter ipw, SparseIntArray array,
String arrayDescription, String keyName, String valueName) {
ipw.print("Number of ");
ipw.print(arrayDescription);
ipw.print(" mappings: ");
ipw.println(array.size());
if (array.size() <= 0) {
return;
}
ipw.increaseIndent();
for (int i = 0; i < array.size(); i++) {
ipw.print(keyName); ipw.print(':');
ipw.print(array.keyAt(i));
ipw.print(" -> ");
ipw.print(valueName); ipw.print(':');
ipw.println(array.valueAt(i));
}
ipw.decreaseIndent();
}
@Override
public void dump(PrintWriter pw, String[] args) {
if (pw instanceof IndentingPrintWriter) {
@@ -445,14 +462,6 @@ public final class UserVisibilityMediator implements Dumpable {
return map;
}
/**
* Gets the user-friendly representation of the {@code result}.
*/
public static String startUserResultToString(@StartUserResult int result) {
return DebugUtils.constantToString(UserVisibilityMediator.class, PREFIX_START_USER_RESULT,
result);
}
// TODO(b/244644281): methods below are needed because some APIs use the current users (full and
// profiles) state to decide whether a user is visible or not. If we decide to always store that
// info into intermediate maps, we should remove them.
@@ -482,6 +491,14 @@ public final class UserVisibilityMediator implements Dumpable {
return profileGroupId != NO_PROFILE_GROUP_ID && profileGroupId != userId;
}
@VisibleForTesting
boolean isStartedUser(@UserIdInt int userId) {
synchronized (mLock) {
return mStartedProfileGroupIds.get(userId,
INITIAL_CURRENT_USER_ID) != INITIAL_CURRENT_USER_ID;
}
}
@VisibleForTesting
boolean isStartedProfile(@UserIdInt int userId) {
int profileGroupId;

View File

@@ -153,14 +153,8 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
}
@Test
public void testUnassignUserFromDisplay() {
testAssignUserToDisplay_displayAvailable();
mMediator.unassignUserFromDisplay(USER_ID);
assertNoUserAssignedToDisplay();
}
// TODO(b/244644281): when start & assign are merged, rename tests above and also call
// stopUserAndAssertState() at the end of them
@Test
public void testIsUserVisible_bgUserOnSecondaryDisplay() {

View File

@@ -15,8 +15,6 @@
*/
package com.android.server.pm;
import static android.os.UserHandle.USER_SYSTEM;
import static org.junit.Assert.assertThrows;
import org.junit.Test;
@@ -34,6 +32,9 @@ public final class UserVisibilityMediatorSUSDTest extends UserVisibilityMediator
super(/* usersOnSecondaryDisplaysEnabled= */ false);
}
// TODO(b/244644281): when start & assign are merged, rename tests below and also call
// stopUserAndAssertState() at the end of them
@Test
public void testAssignUserToDisplay_otherDisplay_currentUser() {
mockCurrentUser(USER_ID);
@@ -59,15 +60,4 @@ public final class UserVisibilityMediatorSUSDTest extends UserVisibilityMediator
assertThrows(UnsupportedOperationException.class, () -> mMediator
.assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID));
}
@Test
public void testUnassignUserFromDisplay_ignored() {
mockCurrentUser(USER_ID);
mMediator.unassignUserFromDisplay(USER_SYSTEM);
mMediator.unassignUserFromDisplay(USER_ID);
mMediator.unassignUserFromDisplay(OTHER_USER_ID);
assertNoUserAssignedToDisplay();
}
}

View File

@@ -20,11 +20,11 @@ import static android.os.UserHandle.USER_NULL;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
import static com.android.server.pm.UserVisibilityMediator.INITIAL_CURRENT_USER_ID;
import static com.android.server.pm.UserVisibilityMediator.START_USER_RESULT_FAILURE;
import static com.android.server.pm.UserVisibilityMediator.START_USER_RESULT_SUCCESS_INVISIBLE;
import static com.android.server.pm.UserVisibilityMediator.START_USER_RESULT_SUCCESS_VISIBLE;
import static com.android.server.pm.UserVisibilityMediator.startUserResultToString;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -100,79 +100,92 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
@Test
public final void testStartUser_currentUser() {
int result = mMediator.startUser(USER_ID, USER_ID, FG, DEFAULT_DISPLAY);
assertStartUserResult(result, START_USER_RESULT_SUCCESS_VISIBLE);
int result = mMediator.startOnly(USER_ID, USER_ID, FG, DEFAULT_DISPLAY);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
assertCurrentUser(USER_ID);
assertIsCurrentUserOrRunningProfileOfCurrentUser(USER_ID);
assertStartedProfileGroupIdOf(USER_ID, USER_ID);
stopUserAndAssertState(USER_ID);
}
@Test
public final void testStartUser_currentUserSecondaryDisplay() {
int result = mMediator.startUser(USER_ID, USER_ID, FG, SECONDARY_DISPLAY_ID);
assertStartUserResult(result, START_USER_RESULT_FAILURE);
int result = mMediator.startOnly(USER_ID, USER_ID, FG, SECONDARY_DISPLAY_ID);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
assertCurrentUser(INITIAL_CURRENT_USER_ID);
assertIsNotCurrentUserOrRunningProfileOfCurrentUser(USER_ID);
assertStartedProfileGroupIdOf(USER_ID, NO_PROFILE_GROUP_ID);
stopUserAndAssertState(USER_ID);
}
@Test
public final void testStartUser_profileBg_parentStarted() {
mockCurrentUser(PARENT_USER_ID);
int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
assertStartUserResult(result, START_USER_RESULT_SUCCESS_VISIBLE);
int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
assertCurrentUser(PARENT_USER_ID);
assertIsCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID);
assertStartedProfileGroupIdOf(PROFILE_USER_ID, PARENT_USER_ID);
assertIsStartedProfile(PROFILE_USER_ID);
assertProfileIsStarted(PROFILE_USER_ID);
stopUserAndAssertState(USER_ID);
}
@Test
public final void testStartUser_profileBg_parentNotStarted() {
int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
assertStartUserResult(result, START_USER_RESULT_SUCCESS_INVISIBLE);
int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
assertCurrentUser(INITIAL_CURRENT_USER_ID);
assertIsNotCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID);
assertStartedProfileGroupIdOf(PROFILE_USER_ID, PARENT_USER_ID);
assertIsStartedProfile(PROFILE_USER_ID);
assertProfileIsStarted(PROFILE_USER_ID);
stopUserAndAssertState(USER_ID);
}
@Test
public final void testStartUser_profileBg_secondaryDisplay() {
int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, SECONDARY_DISPLAY_ID);
assertStartUserResult(result, START_USER_RESULT_FAILURE);
int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, BG, SECONDARY_DISPLAY_ID);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
assertCurrentUser(INITIAL_CURRENT_USER_ID);
assertIsNotCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID);
stopUserAndAssertState(USER_ID);
}
@Test
public final void testStartUser_profileFg() {
int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, FG, DEFAULT_DISPLAY);
assertStartUserResult(result, START_USER_RESULT_FAILURE);
int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, FG, DEFAULT_DISPLAY);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
assertCurrentUser(INITIAL_CURRENT_USER_ID);
assertIsNotCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID);
assertStartedProfileGroupIdOf(PROFILE_USER_ID, NO_PROFILE_GROUP_ID);
stopUserAndAssertState(USER_ID);
}
@Test
public final void testStartUser_profileFgSecondaryDisplay() {
int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, FG, SECONDARY_DISPLAY_ID);
int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, FG, SECONDARY_DISPLAY_ID);
assertStartUserResult(result, START_USER_RESULT_FAILURE);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
assertCurrentUser(INITIAL_CURRENT_USER_ID);
stopUserAndAssertState(USER_ID);
}
@Test
public final void testGetStartedProfileGroupId_whenStartedWithNoProfileGroupId() {
int result = mMediator.startUser(USER_ID, NO_PROFILE_GROUP_ID, FG, DEFAULT_DISPLAY);
assertStartUserResult(result, START_USER_RESULT_SUCCESS_VISIBLE);
int result = mMediator.startOnly(USER_ID, NO_PROFILE_GROUP_ID, FG, DEFAULT_DISPLAY);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
assertWithMessage("shit").that(mMediator.getStartedProfileGroupId(USER_ID))
.isEqualTo(USER_ID);
@@ -386,59 +399,78 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
.isEqualTo(USER_ID);
}
/**
* Stops the given user and assert the proper state is set.
*
* <p>This method should be called at the end of tests that starts a user, so it can test
* {@code stopUser()} as well (technically speaking, {@code stopUser()} should be tested on its
* own methods, but it depends on the user being started at first place, so pragmatically
* speaking, it's better to "reuse" such tests for both (start and stop)
*/
private void stopUserAndAssertState(@UserIdInt int userId) {
mMediator.stopUser(userId);
assertUserIsStopped(userId);
assertNoUserAssignedToDisplay();
}
// TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining
// it's not meant to be used to test startUser() itself.
protected void mockCurrentUser(@UserIdInt int userId) {
Log.d(TAG, "mockCurrentUser(" + userId + ")");
int result = mMediator.startUser(userId, userId, FG, DEFAULT_DISPLAY);
if (result != START_USER_RESULT_SUCCESS_VISIBLE) {
int result = mMediator.startOnly(userId, userId, FG, DEFAULT_DISPLAY);
if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
throw new IllegalStateException("Failed to mock current user " + userId
+ ": mediator returned " + startUserResultToString(result));
+ ": mediator returned " + userAssignmentResultToString(result));
}
}
// TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining
// TODO(b/244644281): remove when start & assign are merged; or add a note explaining
// it's not meant to be used to test startUser() itself.
protected void startDefaultProfile() {
mockCurrentUser(PARENT_USER_ID);
Log.d(TAG, "starting default profile (" + PROFILE_USER_ID + ") in background after starting"
+ " its parent (" + PARENT_USER_ID + ") on foreground");
int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
if (result != START_USER_RESULT_SUCCESS_VISIBLE) {
int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY);
if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
throw new IllegalStateException("Failed to start profile user " + PROFILE_USER_ID
+ ": mediator returned " + startUserResultToString(result));
+ ": mediator returned " + userAssignmentResultToString(result));
}
}
// TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining
// TODO(b/244644281): remove when start & assign are merged; or add a note explaining
// it's not meant to be used to test stopUser() itself.
protected void stopDefaultProfile() {
Log.d(TAG, "stopping default profile");
mMediator.stopUser(PROFILE_USER_ID);
}
// TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining
// TODO(b/244644281): remove when start & assign are merged; or add a note explaining
// it's not meant to be used to test assignUserToDisplay() itself.
protected final void assignUserToDisplay(@UserIdInt int userId, int displayId) {
Log.d(TAG, "assignUserToDisplay(" + userId + ", " + displayId + ")");
int result = mMediator.startUser(userId, userId, BG, displayId);
if (result != START_USER_RESULT_SUCCESS_INVISIBLE) {
int result = mMediator.startOnly(userId, userId, BG, displayId);
if (result != USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE) {
throw new IllegalStateException("Failed to startuser " + userId
+ " on background: mediator returned " + startUserResultToString(result));
+ " on background: mediator returned " + userAssignmentResultToString(result));
}
mMediator.assignUserToDisplay(userId, userId, displayId);
}
// TODO(b/244644281): remove when start & assign are merged; or rename to
// assertNoUserAssignedToSecondaryDisplays
protected final void assertNoUserAssignedToDisplay() {
assertWithMessage("uses on secondary displays")
assertWithMessage("users on secondary displays")
.that(mMediator.getUsersOnSecondaryDisplays())
.isEmpty();
}
// TODO(b/244644281): remove when start & assign are merged; or rename to
// assertUserAssignedToSecondaryDisplay
protected final void assertUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
assertWithMessage("uses on secondary displays")
assertWithMessage("users on secondary displays")
.that(mMediator.getUsersOnSecondaryDisplays())
.containsExactly(userId, displayId);
}
@@ -446,24 +478,44 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
private void assertCurrentUser(@UserIdInt int userId) {
assertWithMessage("mediator.getCurrentUserId()").that(mMediator.getCurrentUserId())
.isEqualTo(userId);
if (userId != INITIAL_CURRENT_USER_ID) {
assertUserIsStarted(userId);
}
}
private void assertIsStartedProfile(@UserIdInt int userId) {
private void assertUserIsStarted(@UserIdInt int userId) {
assertWithMessage("mediator.isStarted(%s)", userId).that(mMediator.isStartedUser(userId))
.isTrue();
}
private void assertUserIsStopped(@UserIdInt int userId) {
assertWithMessage("mediator.isStarted(%s)", userId).that(mMediator.isStartedUser(userId))
.isFalse();
}
private void assertProfileIsStarted(@UserIdInt int userId) {
assertWithMessage("mediator.isStartedProfile(%s)", userId)
.that(mMediator.isStartedProfile(userId))
.isTrue();
assertUserIsStarted(userId);
}
private void assertStartedProfileGroupIdOf(@UserIdInt int profileId, @UserIdInt int parentId) {
assertWithMessage("mediator.getStartedProfileGroupId(%s)", profileId)
.that(mMediator.getStartedProfileGroupId(profileId))
.isEqualTo(parentId);
private void assertStartedProfileGroupIdOf(@UserIdInt int userId,
@UserIdInt int profileGroupId) {
assertWithMessage("mediator.getStartedProfileGroupId(%s)", userId)
.that(mMediator.getStartedProfileGroupId(userId))
.isEqualTo(profileGroupId);
}
private void assertIsCurrentUserOrRunningProfileOfCurrentUser(int userId) {
private void assertIsCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) {
assertWithMessage("mediator.isCurrentUserOrRunningProfileOfCurrentUser(%s)", userId)
.that(mMediator.isCurrentUserOrRunningProfileOfCurrentUser(userId))
.isTrue();
if (mMediator.getCurrentUserId() == userId) {
assertUserIsStarted(userId);
} else {
assertProfileIsStarted(userId);
}
}
private void assertIsNotCurrentUserOrRunningProfileOfCurrentUser(int userId) {
@@ -474,8 +526,8 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
private void assertStartUserResult(int actualResult, int expectedResult) {
assertWithMessage("startUser() result (where %s=%s and %s=%s)",
actualResult, startUserResultToString(actualResult),
expectedResult, startUserResultToString(expectedResult))
actualResult, userAssignmentResultToString(actualResult),
expectedResult, userAssignmentResultToString(expectedResult))
.that(actualResult).isEqualTo(expectedResult);
}
}

View File

@@ -39,6 +39,8 @@ import static com.android.server.am.UserController.USER_CURRENT_MSG;
import static com.android.server.am.UserController.USER_START_MSG;
import static com.android.server.am.UserController.USER_SWITCH_TIMEOUT_MSG;
import static com.android.server.am.UserController.USER_VISIBILITY_CHANGED_MSG;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
import static com.google.android.collect.Lists.newArrayList;
import static com.google.android.collect.Sets.newHashSet;
@@ -100,6 +102,7 @@ import com.android.server.FgThread;
import com.android.server.SystemService;
import com.android.server.am.UserState.KeyEvictedCallback;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
import com.android.server.pm.UserManagerService;
import com.android.server.wm.WindowManagerService;
@@ -162,10 +165,15 @@ public class UserControllerTest {
USER_VISIBILITY_CHANGED_MSG,
USER_CURRENT_MSG);
private static final Set<Integer> START_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
private static final Set<Integer> START_INVISIBLE_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
USER_START_MSG,
REPORT_LOCKED_BOOT_COMPLETE_MSG);
private static final Set<Integer> START_VISIBLE_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
USER_START_MSG,
USER_VISIBILITY_CHANGED_MSG,
REPORT_LOCKED_BOOT_COMPLETE_MSG);
@Before
public void setUp() throws Exception {
runWithDexmakerShareClassLoader(() -> {
@@ -184,6 +192,12 @@ public class UserControllerTest {
mockIsUsersOnSecondaryDisplaysEnabled(false);
// All UserController params are set to default.
// Starts with a generic assumption that the user starts visible, but on tests where
// that's not the case, the test should call mockAssignUserToMainDisplay()
doReturn(UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE)
.when(mInjector.mUserManagerInternalMock)
.assignUserToDisplayOnStart(anyInt(), anyInt(), anyBoolean(), anyInt());
mUserController = new UserController(mInjector);
mUserController.setAllowUserUnlocking(true);
setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS);
@@ -211,15 +225,28 @@ public class UserControllerTest {
@Test
public void testStartUser_background() {
mockAssignUserToMainDisplay(TEST_USER_ID, /* foreground= */ false,
USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
boolean started = mUserController.startUser(TEST_USER_ID, /* foreground= */ false);
assertWithMessage("startUser(%s, foreground=false)", TEST_USER_ID).that(started).isTrue();
verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
verify(mInjector, never()).clearAllLockedTasks(anyString());
startBackgroundUserAssertions();
startBackgroundUserAssertions(/*visible= */ false);
verifyUserAssignedToDisplay(TEST_USER_ID, Display.DEFAULT_DISPLAY);
}
@Test
public void testStartUser_displayAssignmentFailed() {
doReturn(UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE)
.when(mInjector.mUserManagerInternalMock)
.assignUserToDisplayOnStart(eq(TEST_USER_ID), anyInt(), eq(true), anyInt());
boolean started = mUserController.startUser(TEST_USER_ID, /* foreground= */ true);
assertWithMessage("startUser(%s, foreground=true)", TEST_USER_ID).that(started).isFalse();
}
@Test
public void testStartUserOnSecondaryDisplay_defaultDisplay() {
assertThrows(IllegalArgumentException.class, () -> mUserController
@@ -240,7 +267,7 @@ public class UserControllerTest {
verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
verify(mInjector, never()).clearAllLockedTasks(anyString());
startBackgroundUserAssertions();
startBackgroundUserAssertions(/*visible= */ true);
}
@Test
@@ -266,6 +293,8 @@ public class UserControllerTest {
@Test
public void testStartPreCreatedUser_background() throws Exception {
mockAssignUserToMainDisplay(TEST_PRE_CREATED_USER_ID, /* foreground= */ false,
USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
assertTrue(mUserController.startUser(TEST_PRE_CREATED_USER_ID, /* foreground= */ false));
// Make sure no intents have been fired for pre-created users.
assertTrue(mInjector.mSentIntents.isEmpty());
@@ -284,8 +313,6 @@ public class UserControllerTest {
// binder calls, but their side effects (in this case, that the user is stopped right away)
assertWithMessage("wrong binder message calls").that(mInjector.mHandler.getMessageCodes())
.containsExactly(USER_START_MSG);
verifyUserNeverAssignedToDisplay();
}
private void startUserAssertions(
@@ -295,8 +322,10 @@ public class UserControllerTest {
assertEquals("Unexpected message sent", expectedMessageCodes, actualCodes);
}
private void startBackgroundUserAssertions() {
startUserAssertions(START_BACKGROUND_USER_ACTIONS, START_BACKGROUND_USER_MESSAGE_CODES);
private void startBackgroundUserAssertions(boolean visible) {
startUserAssertions(START_BACKGROUND_USER_ACTIONS,
visible ? START_VISIBLE_BACKGROUND_USER_MESSAGE_CODES
: START_INVISIBLE_BACKGROUND_USER_MESSAGE_CODES);
}
private void startForegroundUserAssertions() {
@@ -680,19 +709,24 @@ public class UserControllerTest {
@Test
public void testStartProfile() throws Exception {
mockAssignUserToMainDisplay(TEST_PRE_CREATED_USER_ID, /* foreground= */ false,
USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
setUpAndStartProfileInBackground(TEST_USER_ID1);
startBackgroundUserAssertions();
startBackgroundUserAssertions(/*visible= */ true);
verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
}
@Test
public void testStartProfile_whenUsersOnSecondaryDisplaysIsEnabled() throws Exception {
mockAssignUserToMainDisplay(TEST_USER_ID1, /* foreground= */ false,
USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
mockIsUsersOnSecondaryDisplaysEnabled(true);
setUpAndStartProfileInBackground(TEST_USER_ID1);
startBackgroundUserAssertions();
startBackgroundUserAssertions(/*visible= */ true);
verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
}
@@ -949,22 +983,29 @@ public class UserControllerTest {
when(mInjector.isUsersOnSecondaryDisplaysEnabled()).thenReturn(value);
}
private void mockAssignUserToMainDisplay(@UserIdInt int userId, boolean foreground,
@UserAssignmentResult int result) {
when(mInjector.mUserManagerInternalMock.assignUserToDisplayOnStart(eq(userId),
/* profileGroupId= */ anyInt(), eq(foreground), eq(Display.DEFAULT_DISPLAY)))
.thenReturn(result);
}
private void verifyUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
verify(mInjector.getUserManagerInternal()).assignUserToDisplay(eq(userId), anyInt(),
verify(mInjector.getUserManagerInternal()).assignUserToDisplayOnStart(eq(userId), anyInt(),
anyBoolean(), eq(displayId));
}
private void verifyUserNeverAssignedToDisplay() {
verify(mInjector.getUserManagerInternal(), never()).assignUserToDisplay(anyInt(), anyInt(),
anyBoolean(), anyInt());
verify(mInjector.getUserManagerInternal(), never()).assignUserToDisplayOnStart(anyInt(),
anyInt(), anyBoolean(), anyInt());
}
private void verifyUserUnassignedFromDisplay(@UserIdInt int userId) {
verify(mInjector.getUserManagerInternal()).unassignUserFromDisplay(userId);
verify(mInjector.getUserManagerInternal()).unassignUserFromDisplayOnStop(userId);
}
private void verifyUserUnassignedFromDisplayNeverCalled(@UserIdInt int userId) {
verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplay(userId);
verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplayOnStop(userId);
}
private void verifySystemUserVisibilityChangedNotified(boolean visible) {