Loading services/core/java/com/android/server/am/UserController.java +25 −21 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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, 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"); Loading Loading @@ -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); Loading Loading @@ -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"); Loading services/core/java/com/android/server/pm/UserManagerInternal.java +33 −17 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading Loading @@ -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 Loading Loading @@ -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); Loading services/core/java/com/android/server/pm/UserManagerService.java +4 −5 Original line number Diff line number Diff line Loading @@ -6802,15 +6802,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); } Loading services/core/java/com/android/server/pm/UserVisibilityMediator.java +93 −76 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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); } Loading @@ -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); Loading Loading @@ -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. synchronized (mLock) { if (DBG) { Slogf.d(TAG, "ignoring when device doesn't support MUMD"); 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; } synchronized (mLock) { if (DBG) { Slogf.d(TAG, "Removing %d from mUsersOnSecondaryDisplays (%s)", userId, mUsersOnSecondaryDisplays); Loading Loading @@ -395,30 +405,37 @@ 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(); } Loading @@ -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. Loading Loading @@ -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; Loading services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java +2 −8 Original line number Diff line number Diff line Loading @@ -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() { Loading Loading
services/core/java/com/android/server/am/UserController.java +25 −21 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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, 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"); Loading Loading @@ -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); Loading Loading @@ -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"); Loading
services/core/java/com/android/server/pm/UserManagerInternal.java +33 −17 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading Loading @@ -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 Loading Loading @@ -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); Loading
services/core/java/com/android/server/pm/UserManagerService.java +4 −5 Original line number Diff line number Diff line Loading @@ -6802,15 +6802,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); } Loading
services/core/java/com/android/server/pm/UserVisibilityMediator.java +93 −76 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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); } Loading @@ -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); Loading Loading @@ -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. synchronized (mLock) { if (DBG) { Slogf.d(TAG, "ignoring when device doesn't support MUMD"); 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; } synchronized (mLock) { if (DBG) { Slogf.d(TAG, "Removing %d from mUsersOnSecondaryDisplays (%s)", userId, mUsersOnSecondaryDisplays); Loading Loading @@ -395,30 +405,37 @@ 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(); } Loading @@ -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. Loading Loading @@ -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; Loading
services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java +2 −8 Original line number Diff line number Diff line Loading @@ -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() { Loading