| /* |
| * 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.content.pm.UserInfo.NO_PROFILE_GROUP_ID; |
| import static android.os.UserHandle.USER_NULL; |
| import static android.os.UserHandle.USER_SYSTEM; |
| 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_ALREADY_VISIBLE; |
| 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.USER_START_MODE_BACKGROUND; |
| import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND_VISIBLE; |
| import static com.android.server.pm.UserManagerInternal.USER_START_MODE_FOREGROUND; |
| import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString; |
| import static com.android.server.pm.UserManagerInternal.userStartModeToString; |
| |
| import android.annotation.IntDef; |
| import android.annotation.Nullable; |
| import android.annotation.UserIdInt; |
| import android.os.Handler; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.util.DebugUtils; |
| import android.util.Dumpable; |
| import android.util.EventLog; |
| import android.util.IndentingPrintWriter; |
| import android.util.IntArray; |
| import android.util.SparseIntArray; |
| 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.am.EventLogTags; |
| import com.android.server.pm.UserManagerInternal.UserAssignmentResult; |
| import com.android.server.pm.UserManagerInternal.UserStartMode; |
| import com.android.server.pm.UserManagerInternal.UserVisibilityListener; |
| import com.android.server.utils.Slogf; |
| |
| import java.io.PrintWriter; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| |
| /** |
| * Class responsible for deciding whether a user is visible (or visible for a given display). |
| * |
| * <p>Currently, it has 2 "modes" (set on constructor), which defines the class behavior (i.e, the |
| * logic that dictates the result of methods such as {@link #isUserVisible(int)} and |
| * {@link #isUserVisible(int, int)}): |
| * |
| * <ul> |
| * <li>default: this is the most common mode (used by phones, tablets, foldables, automotives with |
| * just cluster and driver displayes, etc...), where the logic is based solely on the current |
| * foreground user (and its started profiles) |
| * <li>{@code MUMD}: mode for "(concurrent) Multiple Users on Multiple Displays", which is used on |
| * automotives with passenger display. In this mode, users started in background on the secondary |
| * display are stored in map. |
| * </ul> |
| * |
| * <p>This class is thread safe. |
| */ |
| public final class UserVisibilityMediator implements Dumpable { |
| |
| private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE |
| private static final boolean VERBOSE = false; // DO NOT SUBMIT WITH TRUE |
| |
| private static final String TAG = UserVisibilityMediator.class.getSimpleName(); |
| |
| private static final String PREFIX_SECONDARY_DISPLAY_MAPPING = "SECONDARY_DISPLAY_MAPPING_"; |
| public static final int SECONDARY_DISPLAY_MAPPING_NEEDED = 1; |
| public static final int SECONDARY_DISPLAY_MAPPING_NOT_NEEDED = 2; |
| public static final int SECONDARY_DISPLAY_MAPPING_FAILED = -1; |
| |
| /** |
| * Whether a user / display assignment requires adding an entry to the |
| * {@code mUsersOnSecondaryDisplays} map. |
| */ |
| @IntDef(flag = false, prefix = {PREFIX_SECONDARY_DISPLAY_MAPPING}, value = { |
| SECONDARY_DISPLAY_MAPPING_NEEDED, |
| SECONDARY_DISPLAY_MAPPING_NOT_NEEDED, |
| SECONDARY_DISPLAY_MAPPING_FAILED |
| }) |
| public @interface SecondaryDisplayMappingStatus {} |
| |
| // 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; |
| |
| private final Object mLock = new Object(); |
| |
| private final boolean mVisibleBackgroundUsersEnabled; |
| private final boolean mVisibleBackgroundUserOnDefaultDisplayAllowed; |
| |
| @UserIdInt |
| @GuardedBy("mLock") |
| private int mCurrentUserId = INITIAL_CURRENT_USER_ID; |
| |
| /** |
| * Map of background users started visible on displays (key is user id, value is display id). |
| * |
| * <p>Only set when {@code mVisibleBackgroundUsersEnabled} is {@code true}. |
| */ |
| @Nullable |
| @GuardedBy("mLock") |
| private final SparseIntArray mUsersAssignedToDisplayOnStart; |
| |
| /** |
| * Map of extra (i.e., not assigned on start, but by explicit calls to |
| * {@link #assignUserToExtraDisplay(int, int)}) displays assigned to user (key is display id, |
| * value is user id). |
| * |
| * <p>Only set when {@code mVisibleBackgroundUsersEnabled} is {@code true}. |
| */ |
| @Nullable |
| @GuardedBy("mLock") |
| private final SparseIntArray mExtraDisplaysAssignedToUsers; |
| |
| /** |
| * Mapping from each started user to its profile group. |
| */ |
| @GuardedBy("mLock") |
| private final SparseIntArray mStartedProfileGroupIds = new SparseIntArray(); |
| |
| /** |
| * Handler user to call listeners |
| */ |
| private final Handler mHandler; |
| |
| // @GuardedBy("mLock") - hold lock for writes, no lock necessary for simple reads |
| final CopyOnWriteArrayList<UserVisibilityListener> mListeners = |
| new CopyOnWriteArrayList<>(); |
| |
| UserVisibilityMediator(Handler handler) { |
| this(UserManager.isVisibleBackgroundUsersEnabled(), |
| UserManager.isVisibleBackgroundUsersOnDefaultDisplayEnabled(), handler); |
| } |
| |
| @VisibleForTesting |
| UserVisibilityMediator(boolean backgroundUsersOnDisplaysEnabled, |
| boolean visibleBackgroundUserOnDefaultDisplayAllowed, Handler handler) { |
| mVisibleBackgroundUsersEnabled = backgroundUsersOnDisplaysEnabled; |
| mVisibleBackgroundUserOnDefaultDisplayAllowed = |
| visibleBackgroundUserOnDefaultDisplayAllowed; |
| if (mVisibleBackgroundUsersEnabled) { |
| mUsersAssignedToDisplayOnStart = new SparseIntArray(); |
| mExtraDisplaysAssignedToUsers = new SparseIntArray(); |
| } else { |
| mUsersAssignedToDisplayOnStart = null; |
| mExtraDisplaysAssignedToUsers = null; |
| } |
| mHandler = handler; |
| // TODO(b/242195409): might need to change this if boot logic is refactored for HSUM devices |
| mStartedProfileGroupIds.put(INITIAL_CURRENT_USER_ID, INITIAL_CURRENT_USER_ID); |
| } |
| |
| /** |
| * See {@link UserManagerInternal#assignUserToDisplayOnStart(int, int, int, int)}. |
| */ |
| public @UserAssignmentResult int assignUserToDisplayOnStart(@UserIdInt int userId, |
| @UserIdInt int unResolvedProfileGroupId, @UserStartMode int userStartMode, |
| int displayId) { |
| Preconditions.checkArgument(!isSpecialUserId(userId), "user id cannot be generic: %d", |
| userId); |
| // This method needs 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) |
| // |
| // Notice that steps 3 and 4 should be done atomically (i.e., while holding mLock), so the |
| // previous steps are delegated to other methods (canAssignUserToDisplayLocked() and |
| // getUserVisibilityOnStartLocked() respectively). |
| |
| |
| int profileGroupId = unResolvedProfileGroupId == NO_PROFILE_GROUP_ID |
| ? userId |
| : unResolvedProfileGroupId; |
| if (DBG) { |
| Slogf.d(TAG, "assignUserToDisplayOnStart(%d, %d, %s, %d): actualProfileGroupId=%d", |
| userId, unResolvedProfileGroupId, userStartModeToString(userStartMode), |
| displayId, profileGroupId); |
| } |
| |
| int result; |
| IntArray visibleUsersBefore, visibleUsersAfter; |
| synchronized (mLock) { |
| result = getUserVisibilityOnStartLocked(userId, profileGroupId, userStartMode, |
| displayId); |
| if (DBG) { |
| Slogf.d(TAG, "result of getUserVisibilityOnStartLocked(%s)", |
| userAssignmentResultToString(result)); |
| } |
| if (result == USER_ASSIGNMENT_RESULT_FAILURE |
| || result == USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE) { |
| return result; |
| } |
| |
| int mappingResult = canAssignUserToDisplayLocked(userId, profileGroupId, userStartMode, |
| displayId); |
| if (DBG) { |
| Slogf.d(TAG, "mapping result: %s", |
| secondaryDisplayMappingStatusToString(mappingResult)); |
| } |
| if (mappingResult == SECONDARY_DISPLAY_MAPPING_FAILED) { |
| return USER_ASSIGNMENT_RESULT_FAILURE; |
| } |
| |
| visibleUsersBefore = getVisibleUsers(); |
| |
| // Set current user / profiles state |
| if (userStartMode == USER_START_MODE_FOREGROUND) { |
| mCurrentUserId = userId; |
| } |
| if (DBG) { |
| Slogf.d(TAG, "adding user / profile mapping (%d -> %d)", userId, profileGroupId); |
| } |
| mStartedProfileGroupIds.put(userId, profileGroupId); |
| |
| // Set user / display state |
| switch (mappingResult) { |
| case SECONDARY_DISPLAY_MAPPING_NEEDED: |
| if (DBG) { |
| Slogf.d(TAG, "adding user / display mapping (%d -> %d)", userId, displayId); |
| } |
| mUsersAssignedToDisplayOnStart.put(userId, displayId); |
| break; |
| case SECONDARY_DISPLAY_MAPPING_NOT_NEEDED: |
| if (DBG) { |
| // Don't need to do set state because methods (such as isUserVisible()) |
| // already know that the current user (and their profiles) is assigned to |
| // the default display. |
| Slogf.d(TAG, "don't need to update mUsersOnSecondaryDisplays"); |
| } |
| break; |
| default: |
| Slogf.wtf(TAG, "invalid resut from canAssignUserToDisplayLocked: %d", |
| mappingResult); |
| } |
| |
| visibleUsersAfter = getVisibleUsers(); |
| } |
| |
| dispatchVisibilityChanged(visibleUsersBefore, visibleUsersAfter); |
| |
| if (DBG) { |
| Slogf.d(TAG, "returning %s", userAssignmentResultToString(result)); |
| } |
| |
| return result; |
| } |
| |
| @GuardedBy("mLock") |
| @UserAssignmentResult |
| private int getUserVisibilityOnStartLocked(@UserIdInt int userId, @UserIdInt int profileGroupId, |
| @UserStartMode int userStartMode, int displayId) { |
| |
| // Check for invalid combinations first |
| if (userStartMode == USER_START_MODE_BACKGROUND && displayId != DEFAULT_DISPLAY) { |
| Slogf.wtf(TAG, "cannot start user (%d) as BACKGROUND_USER on secondary display (%d) " |
| + "(it should be BACKGROUND_USER_VISIBLE", userId, displayId); |
| return USER_ASSIGNMENT_RESULT_FAILURE; |
| } |
| |
| boolean visibleBackground = userStartMode == USER_START_MODE_BACKGROUND_VISIBLE; |
| if (displayId == DEFAULT_DISPLAY && visibleBackground) { |
| if (mVisibleBackgroundUserOnDefaultDisplayAllowed && isCurrentUserLocked(userId)) { |
| // Shouldn't happen - UserController returns before calling this method |
| Slogf.wtf(TAG, "trying to start current user (%d) visible in background on default" |
| + " display", userId); |
| return USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE; |
| |
| } |
| if (!mVisibleBackgroundUserOnDefaultDisplayAllowed |
| && !isProfile(userId, profileGroupId)) { |
| Slogf.wtf(TAG, "cannot start full user (%d) visible on default display", userId); |
| return USER_ASSIGNMENT_RESULT_FAILURE; |
| } |
| } |
| |
| boolean foreground = userStartMode == USER_START_MODE_FOREGROUND; |
| if (displayId != DEFAULT_DISPLAY) { |
| if (foreground) { |
| Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %b, %d) failed: cannot start " |
| + "foreground user on secondary display", userId, profileGroupId, |
| foreground, displayId); |
| return USER_ASSIGNMENT_RESULT_FAILURE; |
| } |
| if (!mVisibleBackgroundUsersEnabled) { |
| Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %b, %d) failed: called on " |
| + "device that doesn't support multiple users on multiple displays", |
| userId, profileGroupId, foreground, displayId); |
| return USER_ASSIGNMENT_RESULT_FAILURE; |
| } |
| } |
| |
| if (isProfile(userId, profileGroupId)) { |
| if (displayId != DEFAULT_DISPLAY) { |
| Slogf.w(TAG, "canStartUserLocked(%d, %d, %b, %d) failed: cannot start profile user " |
| + "on secondary display", userId, profileGroupId, foreground, |
| displayId); |
| return USER_ASSIGNMENT_RESULT_FAILURE; |
| } |
| if (foreground) { |
| Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start profile user in " |
| + "foreground", userId, profileGroupId, foreground, displayId); |
| return USER_ASSIGNMENT_RESULT_FAILURE; |
| } else { |
| boolean isParentVisibleOnDisplay = isUserVisible(profileGroupId, displayId); |
| if (DBG) { |
| Slogf.d(TAG, "parent visible on display: %b", isParentVisibleOnDisplay); |
| } |
| return isParentVisibleOnDisplay |
| ? USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE |
| : USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE; |
| } |
| } else if (mUsersAssignedToDisplayOnStart != null |
| && isUserAssignedToDisplayOnStartLocked(userId, displayId)) { |
| if (DBG) { |
| Slogf.d(TAG, "full user %d is already visible on display %d", userId, displayId); |
| } |
| return USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE; |
| } |
| |
| return foreground || displayId != DEFAULT_DISPLAY |
| || (visibleBackground && mVisibleBackgroundUserOnDefaultDisplayAllowed) |
| ? USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE |
| : USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE; |
| } |
| |
| @GuardedBy("mLock") |
| @SecondaryDisplayMappingStatus |
| private int canAssignUserToDisplayLocked(@UserIdInt int userId, |
| @UserIdInt int profileGroupId, @UserStartMode int userStartMode, int displayId) { |
| if (displayId == DEFAULT_DISPLAY) { |
| boolean mappingNeeded = false; |
| if (mVisibleBackgroundUserOnDefaultDisplayAllowed |
| && userStartMode == USER_START_MODE_BACKGROUND_VISIBLE) { |
| int userStartedOnDefaultDisplay = getUserStartedOnDisplay(DEFAULT_DISPLAY); |
| if (userStartedOnDefaultDisplay != USER_NULL) { |
| Slogf.w(TAG, "getUserVisibilityOnStartLocked(): cannot start user %d visible on" |
| + " default display because user %d already did so", userId, |
| userStartedOnDefaultDisplay); |
| return SECONDARY_DISPLAY_MAPPING_FAILED; |
| } |
| mappingNeeded = true; |
| } |
| if (!mappingNeeded && mVisibleBackgroundUsersEnabled |
| && isProfile(userId, profileGroupId)) { |
| mappingNeeded = true; |
| } |
| |
| if (!mappingNeeded) { |
| // Don't need to do anything because methods (such as isUserVisible()) already |
| // know that the current user (and its profiles) is assigned to the default display. |
| // But on MUMD devices, profiles are only supported in the default display, so it |
| // cannot return yet as it needs to check if the parent is also assigned to the |
| // DEFAULT_DISPLAY (this is done indirectly below when it checks that the profile |
| // parent is the current user, as the current user is always assigned to the |
| // DEFAULT_DISPLAY). |
| if (DBG) { |
| Slogf.d(TAG, "Ignoring mapping for default display for user %d starting as %s", |
| userId, userStartModeToString(userStartMode)); |
| } |
| return SECONDARY_DISPLAY_MAPPING_NOT_NEEDED; |
| } |
| } |
| |
| if (userId == UserHandle.USER_SYSTEM) { |
| Slogf.w(TAG, "Cannot assign system user to secondary display (%d)", displayId); |
| return SECONDARY_DISPLAY_MAPPING_FAILED; |
| } |
| if (displayId == Display.INVALID_DISPLAY) { |
| Slogf.w(TAG, "Cannot assign to INVALID_DISPLAY (%d)", displayId); |
| return SECONDARY_DISPLAY_MAPPING_FAILED; |
| } |
| if (userId == mCurrentUserId) { |
| Slogf.w(TAG, "Cannot assign current user (%d) to other displays", userId); |
| return SECONDARY_DISPLAY_MAPPING_FAILED; |
| } |
| |
| if (isProfile(userId, profileGroupId)) { |
| // Profile can only start in the same display as parent. And for simplicity, |
| // that display must be the DEFAULT_DISPLAY. |
| if (displayId != Display.DEFAULT_DISPLAY) { |
| Slogf.w(TAG, "Profile user can only be started in the default display"); |
| return SECONDARY_DISPLAY_MAPPING_FAILED; |
| |
| } |
| if (DBG) { |
| Slogf.d(TAG, "Don't need to map profile user %d to default display", userId); |
| } |
| return SECONDARY_DISPLAY_MAPPING_NOT_NEEDED; |
| } |
| |
| if (mUsersAssignedToDisplayOnStart == null) { |
| // Should never have reached this point |
| Slogf.wtf(TAG, "canAssignUserToDisplayLocked(%d, %d, %d, %d) is trying to check " |
| + "mUsersAssignedToDisplayOnStart when it's not set", |
| userId, profileGroupId, userStartMode, displayId); |
| return SECONDARY_DISPLAY_MAPPING_FAILED; |
| } |
| |
| // Check if display is available and user is not assigned to any display |
| for (int i = 0; i < mUsersAssignedToDisplayOnStart.size(); i++) { |
| int assignedUserId = mUsersAssignedToDisplayOnStart.keyAt(i); |
| int assignedDisplayId = mUsersAssignedToDisplayOnStart.valueAt(i); |
| if (DBG) { |
| Slogf.d(TAG, "%d: assignedUserId=%d, assignedDisplayId=%d", |
| i, assignedUserId, assignedDisplayId); |
| } |
| if (displayId == assignedDisplayId) { |
| Slogf.w(TAG, "Cannot assign user %d to display %d because such display is already " |
| + "assigned to user %d", userId, displayId, assignedUserId); |
| return SECONDARY_DISPLAY_MAPPING_FAILED; |
| } |
| if (userId == assignedUserId) { |
| Slogf.w(TAG, "Cannot assign user %d to display %d because such user is as already " |
| + "assigned to display %d", userId, displayId, assignedUserId); |
| return SECONDARY_DISPLAY_MAPPING_FAILED; |
| } |
| } |
| return SECONDARY_DISPLAY_MAPPING_NEEDED; |
| } |
| |
| /** |
| * See {@link UserManagerInternal#assignUserToExtraDisplay(int, int)}. |
| */ |
| public boolean assignUserToExtraDisplay(@UserIdInt int userId, int displayId) { |
| if (DBG) { |
| Slogf.d(TAG, "assignUserToExtraDisplay(%d, %d)", userId, displayId); |
| } |
| if (!mVisibleBackgroundUsersEnabled) { |
| Slogf.w(TAG, "assignUserToExtraDisplay(%d, %d): called when not supported", userId, |
| displayId); |
| return false; |
| } |
| if (displayId == INVALID_DISPLAY) { |
| Slogf.w(TAG, "assignUserToExtraDisplay(%d, %d): called with INVALID_DISPLAY", userId, |
| displayId); |
| return false; |
| } |
| if (displayId == DEFAULT_DISPLAY) { |
| Slogf.w(TAG, "assignUserToExtraDisplay(%d, %d): DEFAULT_DISPLAY is automatically " |
| + "assigned to current user", userId, displayId); |
| return false; |
| } |
| |
| synchronized (mLock) { |
| if (!isUserVisible(userId)) { |
| Slogf.w(TAG, "assignUserToExtraDisplay(%d, %d): failed because user is not visible", |
| userId, displayId); |
| return false; |
| } |
| if (isStartedProfile(userId)) { |
| Slogf.w(TAG, "assignUserToExtraDisplay(%d, %d): failed because user is a profile", |
| userId, displayId); |
| return false; |
| } |
| |
| if (mExtraDisplaysAssignedToUsers.get(displayId, USER_NULL) == userId) { |
| Slogf.w(TAG, "assignUserToExtraDisplay(%d, %d): failed because user is already " |
| + "assigned to that display", userId, displayId); |
| return false; |
| } |
| |
| // First check if the user started on display |
| int userAssignedToDisplay = getUserStartedOnDisplay(displayId); |
| if (userAssignedToDisplay != USER_NULL) { |
| Slogf.w(TAG, "assignUserToExtraDisplay(%d, %d): failed because display was assigned" |
| + " to user %d on start", userId, displayId, userAssignedToDisplay); |
| return false; |
| } |
| // Then if was assigned extra |
| userAssignedToDisplay = mExtraDisplaysAssignedToUsers.get(userId, USER_NULL); |
| if (userAssignedToDisplay != USER_NULL) { |
| Slogf.w(TAG, "assignUserToExtraDisplay(%d, %d): failed because user %d was already " |
| + "assigned that extra display", userId, displayId, userAssignedToDisplay); |
| return false; |
| } |
| if (DBG) { |
| Slogf.d(TAG, "addding %d -> %d to mExtraDisplaysAssignedToUsers", displayId, |
| userId); |
| } |
| mExtraDisplaysAssignedToUsers.put(displayId, userId); |
| } |
| return true; |
| } |
| |
| /** |
| * See {@link UserManagerInternal#unassignUserFromExtraDisplay(int, int)}. |
| */ |
| public boolean unassignUserFromExtraDisplay(@UserIdInt int userId, int displayId) { |
| if (DBG) { |
| Slogf.d(TAG, "unassignUserFromExtraDisplay(%d, %d)", userId, displayId); |
| } |
| if (!mVisibleBackgroundUsersEnabled) { |
| Slogf.w(TAG, "unassignUserFromExtraDisplay(%d, %d): called when not supported", |
| userId, displayId); |
| return false; |
| } |
| synchronized (mLock) { |
| int assignedUserId = mExtraDisplaysAssignedToUsers.get(displayId, USER_NULL); |
| if (assignedUserId == USER_NULL) { |
| Slogf.w(TAG, "unassignUserFromExtraDisplay(%d, %d): not assigned to any user", |
| userId, displayId); |
| return false; |
| } |
| if (assignedUserId != userId) { |
| Slogf.w(TAG, "unassignUserFromExtraDisplay(%d, %d): was assigned to user %d", |
| userId, displayId, assignedUserId); |
| return false; |
| } |
| if (DBG) { |
| Slogf.d(TAG, "removing %d from map", displayId); |
| } |
| mExtraDisplaysAssignedToUsers.delete(displayId); |
| } |
| return true; |
| } |
| |
| /** |
| * See {@link UserManagerInternal#unassignUserFromDisplayOnStop(int)}. |
| */ |
| public void unassignUserFromDisplayOnStop(@UserIdInt int userId) { |
| if (DBG) { |
| Slogf.d(TAG, "unassignUserFromDisplayOnStop(%d)", userId); |
| } |
| IntArray visibleUsersBefore, visibleUsersAfter; |
| synchronized (mLock) { |
| visibleUsersBefore = getVisibleUsers(); |
| |
| unassignUserFromAllDisplaysOnStopLocked(userId); |
| |
| visibleUsersAfter = getVisibleUsers(); |
| } |
| dispatchVisibilityChanged(visibleUsersBefore, visibleUsersAfter); |
| } |
| |
| @GuardedBy("mLock") |
| private void unassignUserFromAllDisplaysOnStopLocked(@UserIdInt int userId) { |
| if (DBG) { |
| Slogf.d(TAG, "Removing %d from mStartedProfileGroupIds (%s)", userId, |
| mStartedProfileGroupIds); |
| } |
| mStartedProfileGroupIds.delete(userId); |
| |
| if (!mVisibleBackgroundUsersEnabled) { |
| // Don't need to update mUsersAssignedToDisplayOnStart 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 user %d from mUsersOnDisplaysMap (%s)", userId, |
| mUsersAssignedToDisplayOnStart); |
| } |
| mUsersAssignedToDisplayOnStart.delete(userId); |
| |
| // Remove extra displays as well |
| for (int i = mExtraDisplaysAssignedToUsers.size() - 1; i >= 0; i--) { |
| if (mExtraDisplaysAssignedToUsers.valueAt(i) == userId) { |
| if (DBG) { |
| Slogf.d(TAG, "Removing display %d from mExtraDisplaysAssignedToUsers (%s)", |
| mExtraDisplaysAssignedToUsers.keyAt(i), mExtraDisplaysAssignedToUsers); |
| } |
| mExtraDisplaysAssignedToUsers.removeAt(i); |
| } |
| } |
| } |
| |
| /** |
| * See {@link UserManagerInternal#isUserVisible(int)}. |
| */ |
| public boolean isUserVisible(@UserIdInt int userId) { |
| // First check current foreground user and their profiles (on main display) |
| if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) { |
| if (VERBOSE) { |
| Slogf.v(TAG, "isUserVisible(%d): true to current user or profile", userId); |
| } |
| return true; |
| } |
| |
| if (!mVisibleBackgroundUsersEnabled) { |
| if (DBG) { |
| Slogf.d(TAG, "isUserVisible(%d): false for non-current user (or its profiles) when" |
| + " device doesn't support visible background users", userId); |
| } |
| return false; |
| } |
| |
| boolean visible; |
| synchronized (mLock) { |
| visible = mUsersAssignedToDisplayOnStart.indexOfKey(userId) >= 0; |
| } |
| if (DBG) { |
| Slogf.d(TAG, "isUserVisible(%d): %b from mapping", userId, visible); |
| } |
| return visible; |
| } |
| |
| @GuardedBy("mLock") |
| private boolean isUserAssignedToDisplayOnStartLocked(@UserIdInt int userId, int displayId) { |
| if (mUsersAssignedToDisplayOnStart == null) { |
| // Shouldn't have been called in this case |
| Slogf.wtf(TAG, "isUserAssignedToDisplayOnStartLocked(%d, %d): called when " |
| + "mUsersAssignedToDisplayOnStart is null", userId, displayId); |
| return false; |
| } |
| boolean isIt = displayId != INVALID_DISPLAY |
| && mUsersAssignedToDisplayOnStart.get(userId, INVALID_DISPLAY) == displayId; |
| if (VERBOSE) { |
| Slogf.v(TAG, "isUserAssignedToDisplayOnStartLocked(%d, %d): %b", userId, displayId, |
| isIt); |
| } |
| return isIt; |
| } |
| |
| /** |
| * See {@link UserManagerInternal#isUserVisible(int, int)}. |
| */ |
| public boolean isUserVisible(@UserIdInt int userId, int displayId) { |
| if (displayId == INVALID_DISPLAY) { |
| return false; |
| } |
| |
| // Current user is always visible on: |
| // - Default display |
| // - Secondary displays when device doesn't support visible bg users |
| // - Or when explicitly added (which is checked below) |
| if (isCurrentUserOrRunningProfileOfCurrentUser(userId) |
| && (displayId == DEFAULT_DISPLAY || !mVisibleBackgroundUsersEnabled)) { |
| if (VERBOSE) { |
| Slogf.v(TAG, "isUserVisible(%d, %d): returning true for current user/profile", |
| userId, displayId); |
| } |
| return true; |
| } |
| |
| if (!mVisibleBackgroundUsersEnabled) { |
| if (DBG) { |
| Slogf.d(TAG, "isUserVisible(%d, %d): returning false as device does not support" |
| + " visible background users", userId, displayId); |
| } |
| return false; |
| } |
| |
| synchronized (mLock) { |
| if (mUsersAssignedToDisplayOnStart.get(userId, Display.INVALID_DISPLAY) == displayId) { |
| // User assigned to display on start |
| return true; |
| } |
| |
| // Check for extra display assignment |
| return mExtraDisplaysAssignedToUsers.get(displayId, USER_NULL) == userId; |
| } |
| } |
| |
| /** |
| * See {@link UserManagerInternal#getDisplayAssignedToUser(int)}. |
| */ |
| public int getDisplayAssignedToUser(@UserIdInt int userId) { |
| if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) { |
| if (mVisibleBackgroundUserOnDefaultDisplayAllowed) { |
| // When device supports visible bg users on default display, the default display is |
| // assigned to the current user, unless a user is started visible on it |
| int userStartedOnDefaultDisplay; |
| synchronized (mLock) { |
| userStartedOnDefaultDisplay = getUserStartedOnDisplay(DEFAULT_DISPLAY); |
| } |
| if (userStartedOnDefaultDisplay != USER_NULL) { |
| if (DBG) { |
| Slogf.d(TAG, "getDisplayAssignedToUser(%d): returning INVALID_DISPLAY for " |
| + "current user user %d was started on DEFAULT_DISPLAY", |
| userId, userStartedOnDefaultDisplay); |
| } |
| return INVALID_DISPLAY; |
| } |
| } |
| return DEFAULT_DISPLAY; |
| } |
| |
| if (!mVisibleBackgroundUsersEnabled) { |
| return INVALID_DISPLAY; |
| } |
| |
| synchronized (mLock) { |
| return mUsersAssignedToDisplayOnStart.get(userId, INVALID_DISPLAY); |
| } |
| } |
| |
| /** |
| * See {@link UserManagerInternal#getUserAssignedToDisplay(int)}. |
| */ |
| public @UserIdInt int getUserAssignedToDisplay(@UserIdInt int displayId) { |
| return getUserAssignedToDisplay(displayId, /* returnCurrentUserByDefault= */ true); |
| } |
| |
| /** |
| * Gets the user explicitly assigned to a display. |
| */ |
| private @UserIdInt int getUserStartedOnDisplay(@UserIdInt int displayId) { |
| return getUserAssignedToDisplay(displayId, /* returnCurrentUserByDefault= */ false); |
| } |
| |
| /** |
| * Gets the user explicitly assigned to a display, or the current user when no user is assigned |
| * to it (and {@code returnCurrentUserByDefault} is {@code true}). |
| */ |
| private @UserIdInt int getUserAssignedToDisplay(@UserIdInt int displayId, |
| boolean returnCurrentUserByDefault) { |
| if (returnCurrentUserByDefault |
| && ((displayId == DEFAULT_DISPLAY && !mVisibleBackgroundUserOnDefaultDisplayAllowed |
| || !mVisibleBackgroundUsersEnabled))) { |
| return getCurrentUserId(); |
| } |
| |
| synchronized (mLock) { |
| for (int i = 0; i < mUsersAssignedToDisplayOnStart.size(); i++) { |
| if (mUsersAssignedToDisplayOnStart.valueAt(i) != displayId) { |
| continue; |
| } |
| int userId = mUsersAssignedToDisplayOnStart.keyAt(i); |
| if (!isStartedProfile(userId)) { |
| return userId; |
| } else if (DBG) { |
| Slogf.d(TAG, "getUserAssignedToDisplay(%d): skipping user %d because it's " |
| + "a profile", displayId, userId); |
| } |
| } |
| } |
| if (!returnCurrentUserByDefault) { |
| if (DBG) { |
| Slogf.d(TAG, "getUserAssignedToDisplay(%d): no user assigned to display, returning " |
| + "USER_NULL instead", displayId); |
| } |
| return USER_NULL; |
| } |
| |
| int currentUserId = getCurrentUserId(); |
| if (DBG) { |
| Slogf.d(TAG, "getUserAssignedToDisplay(%d): no user assigned to display, returning " |
| + "current user (%d) instead", displayId, currentUserId); |
| } |
| return currentUserId; |
| } |
| |
| /** |
| * Gets the ids of the visible users. |
| */ |
| public IntArray getVisibleUsers() { |
| // TODO(b/258054362): this method's performance is O(n2), as it interacts through all users |
| // here, then again on isUserVisible(). We could "fix" it to be O(n), but given that the |
| // number of users is too small, the gain is probably not worth the increase on complexity. |
| IntArray visibleUsers = new IntArray(); |
| synchronized (mLock) { |
| for (int i = 0; i < mStartedProfileGroupIds.size(); i++) { |
| int userId = mStartedProfileGroupIds.keyAt(i); |
| if (isUserVisible(userId)) { |
| visibleUsers.add(userId); |
| } |
| } |
| } |
| return visibleUsers; |
| } |
| |
| /** |
| * Adds a {@link UserVisibilityListener listener}. |
| */ |
| public void addListener(UserVisibilityListener listener) { |
| if (DBG) { |
| Slogf.d(TAG, "adding listener %s", listener); |
| } |
| synchronized (mLock) { |
| mListeners.add(listener); |
| } |
| } |
| |
| /** |
| * Removes a {@link UserVisibilityListener listener}. |
| */ |
| public void removeListener(UserVisibilityListener listener) { |
| if (DBG) { |
| Slogf.d(TAG, "removing listener %s", listener); |
| } |
| synchronized (mLock) { |
| mListeners.remove(listener); |
| } |
| } |
| |
| // TODO(b/242195409): remove this method if not needed anymore |
| /** |
| * Nofify all listeners that the system user visibility changed. |
| */ |
| void onSystemUserVisibilityChanged(boolean visible) { |
| dispatchVisibilityChanged(mListeners, USER_SYSTEM, visible); |
| } |
| |
| /** |
| * Nofify all listeners about the visibility changes from before / after a change of state. |
| */ |
| private void dispatchVisibilityChanged(IntArray visibleUsersBefore, |
| IntArray visibleUsersAfter) { |
| if (visibleUsersBefore == null) { |
| // Optimization - it's only null when listeners is empty |
| if (DBG) { |
| Slogf.d(TAG, "dispatchVisibilityChanged(): ignoring, no listeners"); |
| } |
| return; |
| } |
| CopyOnWriteArrayList<UserVisibilityListener> listeners = mListeners; |
| if (DBG) { |
| Slogf.d(TAG, |
| "dispatchVisibilityChanged(): visibleUsersBefore=%s, visibleUsersAfter=%s, " |
| + "%d listeners (%s)", visibleUsersBefore, visibleUsersAfter, listeners.size(), |
| listeners); |
| } |
| for (int i = 0; i < visibleUsersBefore.size(); i++) { |
| int userId = visibleUsersBefore.get(i); |
| if (visibleUsersAfter.indexOf(userId) == -1) { |
| dispatchVisibilityChanged(listeners, userId, /* visible= */ false); |
| } |
| } |
| for (int i = 0; i < visibleUsersAfter.size(); i++) { |
| int userId = visibleUsersAfter.get(i); |
| if (visibleUsersBefore.indexOf(userId) == -1) { |
| dispatchVisibilityChanged(listeners, userId, /* visible= */ true); |
| } |
| } |
| } |
| |
| private void dispatchVisibilityChanged(CopyOnWriteArrayList<UserVisibilityListener> listeners, |
| @UserIdInt int userId, boolean visible) { |
| EventLog.writeEvent(EventLogTags.UM_USER_VISIBILITY_CHANGED, userId, visible ? 1 : 0); |
| if (DBG) { |
| Slogf.d(TAG, "dispatchVisibilityChanged(%d -> %b): sending to %d listeners", |
| userId, visible, listeners.size()); |
| } |
| for (int i = 0; i < mListeners.size(); i++) { |
| UserVisibilityListener listener = mListeners.get(i); |
| if (VERBOSE) { |
| Slogf.v(TAG, "dispatchVisibilityChanged(%d -> %b): sending to %s", |
| userId, visible, listener); |
| } |
| mHandler.post(() -> listener.onUserVisibilityChanged(userId, visible)); |
| } |
| } |
| |
| private void dump(IndentingPrintWriter ipw) { |
| ipw.println("UserVisibilityMediator"); |
| ipw.increaseIndent(); |
| |
| synchronized (mLock) { |
| ipw.print("Current user id: "); |
| ipw.println(mCurrentUserId); |
| |
| ipw.print("Visible users: "); |
| ipw.println(getVisibleUsers()); |
| |
| dumpSparseIntArray(ipw, mStartedProfileGroupIds, "started user / profile group", |
| "u", "pg"); |
| |
| ipw.print("Supports visible background users on displays: "); |
| ipw.println(mVisibleBackgroundUsersEnabled); |
| |
| ipw.print("Allows visible background users on default display: "); |
| ipw.println(mVisibleBackgroundUserOnDefaultDisplayAllowed); |
| |
| dumpSparseIntArray(ipw, mUsersAssignedToDisplayOnStart, "user / display", "u", "d"); |
| dumpSparseIntArray(ipw, mExtraDisplaysAssignedToUsers, "extra display / user", |
| "d", "u"); |
| |
| int numberListeners = mListeners.size(); |
| ipw.print("Number of listeners: "); |
| ipw.println(numberListeners); |
| if (numberListeners > 0) { |
| ipw.increaseIndent(); |
| for (int i = 0; i < numberListeners; i++) { |
| ipw.print(i); |
| ipw.print(": "); |
| ipw.println(mListeners.get(i)); |
| } |
| ipw.decreaseIndent(); |
| } |
| } |
| |
| ipw.decreaseIndent(); |
| } |
| |
| private static void dumpSparseIntArray(IndentingPrintWriter ipw, @Nullable SparseIntArray array, |
| String arrayDescription, String keyName, String valueName) { |
| if (array == null) { |
| ipw.print("No "); |
| ipw.print(arrayDescription); |
| ipw.println(" mappings"); |
| return; |
| } |
| 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) { |
| dump((IndentingPrintWriter) pw); |
| return; |
| } |
| dump(new IndentingPrintWriter(pw)); |
| } |
| |
| private static boolean isSpecialUserId(@UserIdInt int userId) { |
| switch (userId) { |
| case UserHandle.USER_ALL: |
| case UserHandle.USER_CURRENT: |
| case UserHandle.USER_CURRENT_OR_SELF: |
| case UserHandle.USER_NULL: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| private static boolean isProfile(@UserIdInt int userId, @UserIdInt int profileGroupId) { |
| return profileGroupId != NO_PROFILE_GROUP_ID && profileGroupId != userId; |
| } |
| |
| // NOTE: 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 |
| // mUsersOnSecondaryDisplays, we should remove them. |
| |
| private @UserIdInt int getCurrentUserId() { |
| synchronized (mLock) { |
| return mCurrentUserId; |
| } |
| } |
| |
| @GuardedBy("mLock") |
| private boolean isCurrentUserLocked(@UserIdInt int userId) { |
| // Special case as NO_PROFILE_GROUP_ID == USER_NULL |
| if (userId == USER_NULL || mCurrentUserId == USER_NULL) { |
| return false; |
| } |
| return mCurrentUserId == userId; |
| } |
| |
| private boolean isCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) { |
| synchronized (mLock) { |
| // Special case as NO_PROFILE_GROUP_ID == USER_NULL |
| if (userId == USER_NULL || mCurrentUserId == USER_NULL) { |
| return false; |
| } |
| if (mCurrentUserId == userId) { |
| return true; |
| } |
| return mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID) == mCurrentUserId; |
| } |
| } |
| |
| private boolean isStartedProfile(@UserIdInt int userId) { |
| int profileGroupId; |
| synchronized (mLock) { |
| profileGroupId = mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID); |
| } |
| return isProfile(userId, profileGroupId); |
| } |
| |
| private @UserIdInt int getStartedProfileGroupId(@UserIdInt int userId) { |
| synchronized (mLock) { |
| return mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID); |
| } |
| } |
| |
| private static String secondaryDisplayMappingStatusToString( |
| @SecondaryDisplayMappingStatus int status) { |
| return DebugUtils.constantToString(UserVisibilityMediator.class, |
| PREFIX_SECONDARY_DISPLAY_MAPPING, status); |
| } |
| } |