/*
 * Copyright (C) 2015 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.am;

import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.MANAGE_USERS;
import static android.app.ActivityManager.STOP_USER_ON_SWITCH_DEFAULT;
import static android.app.ActivityManager.STOP_USER_ON_SWITCH_TRUE;
import static android.app.ActivityManager.StopUserOnSwitch;
import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM;
import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
import static android.app.ActivityManager.USER_OP_IS_CURRENT;
import static android.app.ActivityManager.USER_OP_SUCCESS;
import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL;
import static android.app.KeyguardManager.LOCK_ON_USER_SWITCH_CALLBACK;
import static android.os.PowerWhitelistManager.REASON_BOOT_COMPLETED;
import static android.os.PowerWhitelistManager.REASON_LOCKED_BOOT_COMPLETED;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.Process.SHELL_UID;
import static android.os.Process.SYSTEM_UID;

import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__FRAMEWORK_LOCKED_BOOT_COMPLETED;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerService.MY_PID;
import static com.android.server.am.UserState.STATE_BOOTING;
import static com.android.server.am.UserState.STATE_RUNNING_LOCKED;
import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKED;
import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKING;
import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_ABORTED;
import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_INVALID_SESSION_ID;
import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_BEGIN;
import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_FINISH;
import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_NONE;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_START;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_STOP;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_SWITCH_FG;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_SWITCH_UI;
import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_UNLOCKED_USER;
import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_UNLOCKING_USER;
import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
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.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SpecialUsers.CanBeALL;
import android.annotation.SpecialUsers.CanBeCURRENT;
import android.annotation.SpecialUsers.CanBeCURRENT_OR_SELF;
import android.annotation.SpecialUsers.CanBeNULL;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.app.IStopUserCallback;
import android.app.IUserSwitchObserver;
import android.app.KeyguardManager;
import android.app.usage.UsageEvents;
import android.appwidget.AppWidgetManagerInternal;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.PermissionChecker;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackagePartitions;
import android.content.pm.UserInfo;
import android.content.pm.UserProperties;
import android.media.AudioManagerInternal;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
import android.os.IProgressListener;
import android.os.IRemoteCallback;
import android.os.IUserManager;
import android.os.Message;
import android.os.PowerManagerInternal;
import android.os.PowerWhitelistManager;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArraySet;
import android.util.EventLog;
import android.util.IntArray;
import android.util.Pair;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
import android.window.DesktopExperienceFlags;

import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.ObjectUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.AlarmManagerInternal;
import com.android.server.FactoryResetter;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService.UserCompletedEventType;
import com.android.server.SystemServiceManager;
import com.android.server.am.UserState.KeyEvictedCallback;
import com.android.server.locksettings.LockSettingsInternal;
import com.android.server.pm.UserJourneyLogger;
import com.android.server.pm.UserJourneyLogger.UserJourneySession;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
import com.android.server.pm.UserManagerInternal.UserStartMode;
import com.android.server.pm.UserManagerService;
import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.wm.ActivityAssistInfo;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerService;

import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;

/**
 * Helper class for {@link ActivityManagerService} responsible for multi-user functionality.
 *
 * <p>This class use {@link #mLock} to synchronize access to internal state. Methods that require
 * {@link #mLock} to be held should have "LU" suffix in the name.
 *
 * <p><strong>Important:</strong> Synchronized code, i.e. one executed inside a synchronized(mLock)
 * block or inside LU method, should only access internal state of this class or make calls to
 * other LU methods. Non-LU method calls or calls to external classes are discouraged as they
 * may cause lock inversion.
 */
class UserController implements Handler.Callback {
    private static final String TAG = UserController.class.getSimpleName();

    // Amount of time we wait for observers to handle a user switch before
    // giving up on them and dismissing the user switching dialog.
    static final int DEFAULT_USER_SWITCH_TIMEOUT_MS = 3 * 1000;

    /**
     * Amount of time we wait for an observer to handle a user switch before we log a warning. This
     * wait time is per observer.
     */
    private static final int LONG_USER_SWITCH_OBSERVER_WARNING_TIME_MS = 500;

    // ActivityManager thread message constants
    static final int REPORT_USER_SWITCH_MSG = 10;
    static final int CONTINUE_USER_SWITCH_MSG = 20;
    static final int USER_SWITCH_TIMEOUT_MSG = 30;
    static final int START_PROFILES_MSG = 40;
    static final int USER_START_MSG = 50;
    static final int USER_CURRENT_MSG = 60;
    static final int FOREGROUND_PROFILE_CHANGED_MSG = 70;
    static final int REPORT_USER_SWITCH_COMPLETE_MSG = 80;
    static final int USER_SWITCH_CALLBACKS_TIMEOUT_MSG = 90;
    static final int USER_UNLOCK_MSG = 100;
    static final int USER_UNLOCKED_MSG = 105;
    static final int REPORT_LOCKED_BOOT_COMPLETE_MSG = 110;
    static final int START_USER_SWITCH_FG_MSG = 120;
    static final int COMPLETE_USER_SWITCH_MSG = 130;
    static final int USER_COMPLETED_EVENT_MSG = 140;
    static final int SCHEDULE_STOP_BACKGROUND_USER_MSG = 150;
    static final int SCHEDULE_JUDGE_FATE_OF_BACKGROUND_USER_MSG = 160;
    static final int USER_SWITCHING_DIALOG_ANIMATION_TIMEOUT_MSG = 170;

    private static final int NO_ARG2 = 0;

    // Message constant to clear {@link UserJourneySession} from {@link mUserIdToUserJourneyMap} if
    // the user journey, defined in the UserLifecycleJourneyReported atom for statsd, is not
    // complete within {@link USER_JOURNEY_TIMEOUT}.
    static final int CLEAR_USER_JOURNEY_SESSION_MSG = 200;
    // Wait time for completing the user journey. If a user journey is not complete within this
    // time, the remaining lifecycle events for the journey would not be logged in statsd.
    // Timeout set for 90 seconds.
    private static final int USER_JOURNEY_TIMEOUT_MS = 90_000;

    // UI thread message constants
    static final int START_USER_SWITCH_UI_MSG = 1000;

    /**
     * If a callback wasn't called within USER_SWITCH_CALLBACKS_TIMEOUT_MS after
     * {@link #getUserSwitchTimeoutMs}, an error is reported. Usually it indicates a problem in the
     * observer when it never calls back.
     */
    private static final int USER_SWITCH_CALLBACKS_TIMEOUT_MS = 5 * 1000;

    /**
     * Time after last scheduleOnUserCompletedEvent() call at which USER_COMPLETED_EVENT_MSG will be
     * scheduled (although it may fire sooner instead).
     * When it fires, {@link #reportOnUserCompletedEvent} will be processed.
     */
    // TODO(b/197344658): Increase to 10s or 15s once we have a switch-UX-is-done invocation too.
    private static final int USER_COMPLETED_EVENT_DELAY_MS = 5 * 1000;

    /**
     * If a user has an alarm in the next this many milliseconds, avoid stopping it due to
     * scheduled background stopping.
     */
    private static final long TIME_BEFORE_USERS_ALARM_TO_AVOID_STOPPING_MS = 60 * 60_000; // 60 mins

    /**
     * If a background user stop is requested, but doing so right now isn't suitable (e.g. because
     * it is currently playing audio) and we have to reschedule that background stop, we should
     * schedule that stop for this many seconds from now.
     */
    private static final int POSTPONEMENT_TIME_FOR_BACKGROUND_USER_STOP_SECS = 10 * 60; // 10 mins

    /**
     * How many seconds to wait until checking whether there has been lmk activity, as required when
     * passing judgement on whether to automatically stop a background user.
     * This is actually used for two distinct (but related) purposes:
     * <ul>
     * <li>to wait until initial process states settle after the user enters the background
     * <li>polling frequency to get the latest lmkCount values for subsequent judgements
     *</ul>
     * <p>This is NOT directly related to config_backgroundUserConsideredDispensableTimeSecs, which
     * is generally a much longer time indicating when the background user is declared dispensable.
     */
    private static final long POLLING_TIME_FOR_JUDGING_BACKGROUND_USER_SECS = 10 * 60; // 10 mins

    @VisibleForTesting
    static final String EXCEPTION_TEMPLATE_CANNOT_START_USER_WHEN_NOT_READY =
            "Trying to start user %d before ready";

    /**
     * Maximum number of users we allow to be running at a time, including system user.
     *
     * <p>This parameter only affects how many background users will be stopped when
     * switching to, or starting, a new user. It does not prevent starting a user.
     *
     * <p>Note: This is a soft limit; the actual number of running users can exceed mMaxRunningUsers
     * in several situations. For example: <ul>
     * <li>The current user and its related profiles are never stopped when switching/starting
     *     users, even if it has more profiles than mMaxRunningUsers.
     * <li>If a background user is deemed important (e.g. because it is visible or audible), we may
     *     abstain from or defer stopping it, even if that causes us to exceed mMaxRunningUsers.
     * </ul>
     */
    @GuardedBy("mLock")
    private int mMaxRunningUsers;

    /**
     * Number of seconds of uptime after a full user enters the background before we consider
     * it as no longer important, and therefore can consider automatically stopping it.
     * Set to -1 to disable this feature (i.e. don't auto-stop background users after some elapsed
     * time).
     * Typically set by config_backgroundUserConsideredDispensableTimeSecs.
     */
    private int mBackgroundUserConsideredDispensableTimeSecs = -1;

    // Lock for internal state.
    private final Object mLock = new Object();

    private final Injector mInjector;
    private final Handler mHandler;
    private final Handler mUiHandler;

    // Holds the current foreground user's id. Use mLock when updating
    @GuardedBy("mLock")
    private volatile int mCurrentUserId = UserHandle.USER_SYSTEM;
    // Holds the target user's id during a user switch. The value of mCurrentUserId will be updated
    // once target user goes into the foreground. Use mLock when updating
    @GuardedBy("mLock")
    private volatile int mTargetUserId = UserHandle.USER_NULL;
    // If a user switch request comes during an ongoing user switch, it is postponed to the end of
    // the current switch, and this variable holds those user ids. Use mLock when updating
    @GuardedBy("mLock")
    private final ArrayDeque<Integer> mPendingTargetUserIds = new ArrayDeque<>();

    /**
     * Which users have been started, so are allowed to run code.
     */
    @GuardedBy("mLock")
    private final SparseArray<UserState> mStartedUsers = new SparseArray<>();

    // If logging out of a user cannot be immediately done, because we are in the process of
    // switching to that same user and cannot stop that user, then we flip this flag to true, so
    // that when the ongoing user-switch is finished, we would stop the user then.
    @GuardedBy("mLock")
    private boolean mPendingLogoutTargetUser;

    /**
     * LRU list of history of running users, in order of when we last needed to start them.
     *
     * Switching to a user will move it towards the end. Attempting to start a user/profile (even
     * if it was already running) will move it towards the end.
     *
     * <p>Guarantees (by the end of startUser):
     * <li>The current user will always be at the end, even if background users were started
     * subsequently.
     * <li>Parents always come later than (but not necessarily adjacent to) their profiles.
     */
    @GuardedBy("mLock")
    private final ArrayList<Integer> mUserLru = new ArrayList<>();

    /**
     * Constant array of the users that are currently started.
     */
    @GuardedBy("mLock")
    private int[] mStartedUserArray = new int[] { 0 };

    /**
     * Contains the current user and its profiles (if any).
     *
     * <p><b>NOTE: </b>it lists all profiles, regardless of their running state (i.e., they're in
     * this list even if not running).
     */
    @GuardedBy("mLock")
    private int[] mCurrentProfileIds = new int[] {};

    /**
     * Mapping from each known user ID to the profile group ID it is associated with.
     * <p>Users not present in this array have a profile group of NO_PROFILE_GROUP_ID.
     * <p>Users present in this array do not have a profile group of NO_PROFILE_GROUP_ID.
     *
     * <p>For better or worse, this class sometimes assumes that the profileGroupId of a parent user
     * is always identical with its userId. If that ever becomes false, this class needs updating.
     */
    @GuardedBy("mLock")
    private final SparseIntArray mUserProfileGroupIds = new SparseIntArray();

    /**
     * Registered observers of the user switching mechanics.
     */
    private final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers
            = new RemoteCallbackList<>();

    @GuardedBy("mLock")
    private boolean mUserSwitchUiEnabled = true;

    /**
     * Currently active user switch callbacks.
     */
    @GuardedBy("mLock")
    private volatile ArraySet<String> mCurWaitingUserSwitchCallbacks;

    /**
     * Message shown when switching from a user.
     */
    @GuardedBy("mLock")
    private final SparseArray<String> mSwitchingFromUserMessage = new SparseArray<>();

    /**
     * Message shown when switching to a user.
     */
    @GuardedBy("mLock")
    private final SparseArray<String> mSwitchingToUserMessage = new SparseArray<>();

    /**
     * Callbacks that are still active after {@link #getUserSwitchTimeoutMs}
     */
    @GuardedBy("mLock")
    private ArraySet<String> mTimeoutUserSwitchCallbacks;

    private final LockPatternUtils mLockPatternUtils;

    // TODO(b/266158156): remove this once we improve/refactor the way broadcasts are sent for
    //  the system user in HSUM.
    @GuardedBy("mLock")
    private boolean mIsBroadcastSentForSystemUserStarted;

    // TODO(b/266158156): remove this once we improve/refactor the way broadcasts are sent for
    //  the system user in HSUM.
    @GuardedBy("mLock")
    private boolean mIsBroadcastSentForSystemUserStarting;

    /**
     * In this mode, user is always stopped when switched out (unless overridden by the
     * {@code fw.stop_bg_users_on_switch} system property) but locking of user data is
     * postponed until total number of unlocked users in the system reaches mMaxRunningUsers.
     * Once total number of unlocked users reach mMaxRunningUsers, least recently used user
     * will be locked.
     *
     * <p> Note: Even if this is false for the device as a whole, it is possible some users may
     * individually allow delayed locking, as specified by
     * {@link UserProperties#getAllowStoppingUserWithDelayedLocking()}.
     */
    @GuardedBy("mLock")
    private boolean mDelayUserDataLocking;

    /**
     * Users are only allowed to be unlocked after boot complete.
     */
    private volatile boolean mAllowUserUnlocking;

    /**
     * Keep track of last active users for delayUserDataLocking.
     * The most recently stopped user with delayed locking is placed in front, while the least
     * recently stopped user in back.
     */
    @GuardedBy("mLock")
    private final ArrayList<Integer> mLastActiveUsersForDelayedLocking = new ArrayList<>();

    /**
     * Map of userId to {@link UserCompletedEventType} event flags, indicating which as-yet-
     * unreported user-starting events have transpired for the given user.
     */
    @GuardedBy("mCompletedEventTypes")
    private final SparseIntArray mCompletedEventTypes = new SparseIntArray();

    /**
     * Sets on {@link #setInitialConfig(boolean, int, boolean, int)}, which is called by
     * {@code ActivityManager} when the system is started.
     *
     * <p>It's useful to ignore external operations (i.e., originated outside {@code system_server},
     * like from {@code adb shell am switch-user})) that could happen before such call is made and
     * the system is ready.
     */
    @GuardedBy("mLock")
    private boolean mInitialized;

    /**
     * Defines the behavior of whether the background users should be stopped when the foreground
     * user is switched.
     */
    @GuardedBy("mLock")
    private @StopUserOnSwitch int mStopUserOnSwitch = STOP_USER_ON_SWITCH_DEFAULT;

    /** @see #getLastUserUnlockingUptime */
    private volatile long mLastUserUnlockingUptime = 0;

    /**
     * Pending user starts waiting for shutdown step to complete.
     */
    @GuardedBy("mLock")
    private final List<PendingUserStart> mPendingUserStarts = new ArrayList<>();

    /**
     * Contains users which cannot abort the shutdown process.
     *
     * <p> For example, we don't abort shutdown for users whose processes have already been stopped
     * due to {@link #isEarlyPackageKillEnabledForUserSwitch(int, int)}.
     */
    @GuardedBy("mLock")
    private final ArraySet<Integer> mDoNotAbortShutdownUserIds = new ArraySet<>();

    private final UserLifecycleListener mUserLifecycleListener = new UserLifecycleListener() {
        @Override
        public void onUserCreated(UserInfo user, Object token) {
            onUserAdded(user);
        }
    };

    /**
     * Used to prevent external calls (for example, from {@code am start-user}) from crashing the
     * system when it's not ready yet.
     */
    @GuardedBy("mLock")
    private boolean mReady;

    UserController(ActivityManagerService service) {
        this(new Injector(service));
    }

    @VisibleForTesting
    UserController(Injector injector) {
        mInjector = injector;
        // This should be called early to avoid a null mHandler inside the injector
        mHandler = mInjector.getHandler(this);
        mUiHandler = mInjector.getUiHandler(this);
        // User 0 is the first and only user that runs at boot.
        final UserState uss = new UserState(UserHandle.SYSTEM);
        uss.mUnlockProgress.addListener(new UserProgressListener());
        mStartedUsers.put(UserHandle.USER_SYSTEM, uss);
        mUserLru.add(UserHandle.USER_SYSTEM);
        mLockPatternUtils = mInjector.getLockPatternUtils();
        updateStartedUserArrayLU();
    }

    void setInitialConfig(boolean userSwitchUiEnabled, int maxRunningUsers,
            boolean delayUserDataLocking, int backgroundUserConsideredDispensableTimeSecs) {
        synchronized (mLock) {
            mUserSwitchUiEnabled = userSwitchUiEnabled;
            mMaxRunningUsers = maxRunningUsers;
            mDelayUserDataLocking = delayUserDataLocking;
            if (android.multiuser.Flags.scheduleStopOfBackgroundUserByDefault()) {
                // If flag is off, the default value of -1 applies, disabling the feature.
                mBackgroundUserConsideredDispensableTimeSecs
                        = backgroundUserConsideredDispensableTimeSecs;
            }
            mInitialized = true;
        }
    }

    private boolean isUserSwitchUiEnabled() {
        synchronized (mLock) {
            return mUserSwitchUiEnabled;
        }
    }

    int getMaxRunningUsers() {
        synchronized (mLock) {
            return mMaxRunningUsers;
        }
    }

    void setStopUserOnSwitch(@StopUserOnSwitch int value) {
        if (mInjector.checkCallingPermission(android.Manifest.permission.MANAGE_USERS)
                == PackageManager.PERMISSION_DENIED && mInjector.checkCallingPermission(
                android.Manifest.permission.INTERACT_ACROSS_USERS)
                == PackageManager.PERMISSION_DENIED) {
            throw new SecurityException(
                    "You either need MANAGE_USERS or INTERACT_ACROSS_USERS permission to "
                            + "call setStopUserOnSwitch()");
        }

        synchronized (mLock) {
            Slogf.i(TAG, "setStopUserOnSwitch(): %d -> %d", mStopUserOnSwitch, value);
            mStopUserOnSwitch = value;
        }
    }

    private boolean isStopUserOnSwitchEnabled() {
        synchronized (mLock) {
            if (mStopUserOnSwitch != STOP_USER_ON_SWITCH_DEFAULT) {
                final boolean value = mStopUserOnSwitch == STOP_USER_ON_SWITCH_TRUE;
                Slogf.i(TAG, "isStopUserOnSwitchEnabled(): returning overridden value (%b)", value);
                return value;
            }
        }
        final int property = SystemProperties.getInt("fw.stop_bg_users_on_switch", -1);
        return property == -1 ? mDelayUserDataLocking : property == 1;
    }

    /**
     * Get whether or not the previous user's packages will be killed before the user is
     * stopped during a user switch.
     *
     * <p> The primary use case of this method is for {@link com.android.server.SystemService}
     * classes to call this API in their
     * {@link com.android.server.SystemService#onUserSwitching} method implementation to prevent
     * restarting any of the previous user's processes that will be killed during the user switch.
     */
    boolean isEarlyPackageKillEnabledForUserSwitch(int fromUserId, int toUserId) {
        // NOTE: The logic in this method could be extended to cover other cases where
        // the previous user is also stopped like: guest users, ephemeral users,
        // and users with DISALLOW_RUN_IN_BACKGROUND. Currently, this is not done
        // because early killing is not enabled for these cases by default.
        if (fromUserId == UserHandle.USER_SYSTEM) {
            return false;
        }
        return isStopUserOnSwitchEnabled();
    }

    void finishUserSwitch(UserState uss) {
        // This call holds the AM lock so we post to the handler.
        mHandler.post(() -> {
            finishUserBoot(uss);
            startProfiles();
            stopExcessRunningUsers();
        });
    }

    /** Adds a user to mUserLru, moving it to the end of the list if it was already present. */
    private void addUserToUserLru(@UserIdInt int userId) {
        synchronized (mLock) {
            final Integer userIdObj = userId;
            mUserLru.remove(userIdObj);
            mUserLru.add(userIdObj);

            // Now also move the user's parent to the end (if applicable).
            Integer parentIdObj = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
            if (parentIdObj != UserInfo.NO_PROFILE_GROUP_ID && !parentIdObj.equals(userIdObj)
                    && mUserLru.remove(parentIdObj)) {
                mUserLru.add(parentIdObj);
            }
        }
    }

    /** Returns a list of running users, in order of when they were started (oldest first). */
    @GuardedBy("mLock")
    @VisibleForTesting
    List<Integer> getRunningUsersLU() {
        ArrayList<Integer> runningUsers = new ArrayList<>();
        for (Integer userId : mUserLru) {
            UserState uss = mStartedUsers.get(userId);
            if (uss == null) {
                // Shouldn't happen, but recover if it does.
                continue;
            }
            if (uss.state == UserState.STATE_STOPPING
                    || uss.state == UserState.STATE_SHUTDOWN) {
                // This user is already stopping, doesn't count.
                continue;
            }
            runningUsers.add(userId);
        }
        return runningUsers;
    }

    private void stopExcessRunningUsers() {
        stopExcessRunningUsers(UserHandle.USER_NULL);
    }

    private void stopExcessRunningUsers(@CanBeNULL @UserIdInt int exemptedUserId) {
        final ArraySet<Integer> exemptedUsers = new ArraySet<>();
        final ArraySet<Integer> avoidUsers = new ArraySet<>();

        if (exemptedUserId != UserHandle.USER_NULL) {
            exemptedUsers.add(exemptedUserId);
        }

        final List<UserInfo> users = mInjector.getUserManager().getUsers(true);
        final ArraySet<Integer> visibleActivityUsers = mInjector.getVisibleActivityUsers();
        for (int i = 0; i < users.size(); i++) {
            final int userId = users.get(i).id;
            if (isUserVisible(userId)) {
                exemptedUsers.add(userId);
            } else if (avoidStoppingUserRightNow(userId, visibleActivityUsers)) {
                avoidUsers.add(userId);
            }
        }

        synchronized (mLock) {
            stopExcessRunningUsersLU(mMaxRunningUsers, exemptedUsers, avoidUsers);
        }
    }

    /**
     * Attempts to stop users (based on {@link #getRunningUsersLU() when they were last started})
     * until the total running users is within maxRunningUsers.
     *
     * @param exemptedUsers users that must not be stopped by this method
     * @param avoidUsers users that we should avoid stopping if possible, but can be scheduled for
     *                   stopping later if necessary.
     */
    @GuardedBy("mLock")
    private void stopExcessRunningUsersLU(int maxRunningUsers,
            ArraySet<Integer> exemptedUsers, ArraySet<Integer> avoidUsers) {

        List<Integer> candidatesForScheduledStopping = new ArrayList<>(avoidUsers.size());

        List<Integer> currentlyRunningLru = getRunningUsersLU();
        Iterator<Integer> iterator = currentlyRunningLru.iterator();
        while (currentlyRunningLru.size() > maxRunningUsers && iterator.hasNext()) {
            final Integer userId = iterator.next();
            if (userId == UserHandle.USER_SYSTEM
                    || userId == mCurrentUserId
                    || exemptedUsers.contains(userId)) {
                // System and current users can't be stopped, and an exempt user shouldn't be
                continue;
            }
            if (avoidUsers.contains(userId)) {
                // Try not to stop these users. Keep track (in order) in case second pass is needed.
                Slogf.i(TAG, "Avoiding stopping user %d", userId);
                candidatesForScheduledStopping.add(userId);
                continue;
            }
            // allowDelayedLocking set here as stopping user is done without any explicit request
            // from outside.
            Slogf.i(TAG, "%d too many running users (%d total). Attempting to stop user %d.",
                    currentlyRunningLru.size() - maxRunningUsers,
                    currentlyRunningLru.size(), userId);
            if (stopUsersLU(userId,
                    /* stopProfileRegardlessOfParent= */ false, /* allowDelayedLocking= */ true,
                    /* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
                    == USER_OP_SUCCESS) {
                // Technically, stopUsersLU can remove more than one user when stopping a parent.
                // But mUserLru is designed so that profiles always precede their parent, so this
                // normally won't happen here, and therefore won't cause underestimating the number
                // removed.
                iterator.remove();
            }
        }

        // If necessary, do a second pass, on candidatesForScheduledStopping.
        int excessUsers = currentlyRunningLru.size() - maxRunningUsers;
        for (int i = 0; excessUsers > 0 && i < candidatesForScheduledStopping.size(); i++) {
            final Integer userId = candidatesForScheduledStopping.get(i);
            Slogf.i(TAG, "%d too many running users. Scheduling to later stop user %d.",
                    excessUsers, userId);
            if (rescheduleStopOfBackgroundUser(userId)) {
                excessUsers--;
            }
        }

        if (excessUsers > 0) {
            Slogf.i(TAG, "%d too many running users. We are left in a state exceeding %d "
                    + "running users, but that's okay because it is a soft limit.",
                    excessUsers, maxRunningUsers);
        }
    }

    /**
     * Returns if more users can be started without stopping currently running users.
     */
    boolean canStartMoreUsers() {
        synchronized (mLock) {
            return getRunningUsersLU().size() < mMaxRunningUsers;
        }
    }

    private void finishUserBoot(UserState uss) {
        finishUserBoot(uss, null);
    }

    private void finishUserBoot(UserState uss, IIntentReceiver resultTo) {
        final int userId = uss.mHandle.getIdentifier();
        EventLog.writeEvent(EventLogTags.UC_FINISH_USER_BOOT, userId);

        synchronized (mLock) {
            // Bail if we ended up with a stale user
            if (mStartedUsers.get(userId) != uss) {
                return;
            }
        }

        // We always walk through all the user lifecycle states to send
        // consistent developer events. We step into RUNNING_LOCKED here,
        // but we might immediately step into RUNNING below if the user
        // storage is already unlocked.
        if (uss.setState(STATE_BOOTING, STATE_RUNNING_LOCKED)) {
            mInjector.getUserJourneyLogger()
                    .logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
                    EVENT_STATE_NONE);
            mInjector.getUserManagerInternal().setUserState(userId, uss.state);
            // Do not report secondary users, runtime restarts or first boot/upgrade
            if (userId == UserHandle.USER_SYSTEM
                    && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
                final long elapsedTimeMs = SystemClock.elapsedRealtime();
                FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
                        BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__FRAMEWORK_LOCKED_BOOT_COMPLETED,
                        elapsedTimeMs);
                final long maxElapsedTimeMs = 120_000;
                if (elapsedTimeMs > maxElapsedTimeMs) {
                    Slogf.wtf("SystemServerTiming",
                            "finishUserBoot took too long. elapsedTimeMs=" + elapsedTimeMs);
                }
            }

            if (!mInjector.getUserManager().isPreCreated(userId)) {
                mHandler.sendMessage(mHandler.obtainMessage(REPORT_LOCKED_BOOT_COMPLETE_MSG,
                        userId, 0));
                // The "locked boot complete" broadcast for the system user is supposed be sent when
                // the device has finished booting.  Normally, that is the same time that the system
                // user transitions to RUNNING_LOCKED.  However, in "headless system user mode", the
                // system user is explicitly started before the device has finished booting.  In
                // that case, we need to wait until onBootComplete() to send the broadcast.
                // Similarly, this occurs after a user switch, but in HSUM we switch to the main
                // user before boot is complete, so again this should be delayed until
                // onBootComplete if boot has not yet completed.
                if (mAllowUserUnlocking) {
                    // ACTION_LOCKED_BOOT_COMPLETED
                    sendLockedBootCompletedBroadcast(resultTo, userId);
                }
            }
        }

        // We need to delay unlocking profiles until the parent user is also unlocked.
        final UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
        if (parent == null) {
            // Not a profile (or is a parentless profile) so no parent for which to wait.
            maybeUnlockUser(userId);
        } else if (isUserRunning(parent.id, ActivityManager.FLAG_AND_UNLOCKED)) {
            Slogf.d(TAG, "User " + userId + " (parent " + parent.id
                    + "): attempting unlock because parent is unlocked");
            maybeUnlockUser(userId);
        } else {
            Slogf.d(TAG, "User " + userId + " (parent " + parent.id
                    + "): delaying unlock because parent is locked");
        }
    }

    private void sendLockedBootCompletedBroadcast(IIntentReceiver receiver, @UserIdInt int userId) {
        final Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null);
        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
        intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
                | Intent.FLAG_RECEIVER_OFFLOAD
                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);

        final UserInfo userInfo = getUserInfo(userId);
        if (userInfo != null && userInfo.isPrivateProfile()) {
            sendBroadcastLockedBootCompleteForPrivateProfileApps(intent, userId, receiver);
            return;
        }

        mInjector.broadcastIntent(intent, null, receiver, 0, null, null,
                new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
                AppOpsManager.OP_NONE,
                getTemporaryAppAllowlistBroadcastOptions(REASON_LOCKED_BOOT_COMPLETED)
                        .toBundle(),
                false, MY_PID, SYSTEM_UID,
                Binder.getCallingUid(), Binder.getCallingPid(), userId);
    }

    /**
     * Only broadcast the LOCKED_BOOT_COMPLETED intent to allowlisted immediate receivers. Other
     * packages will receive the intent after they are started.
     */
    private void sendBroadcastLockedBootCompleteForPrivateProfileApps(Intent bootIntent,
            int userId,
            IIntentReceiver receiver) {
        if (!android.multiuser.Flags.enableMovingContentIntoPrivateSpace()) {
            Slogf.i(TAG, "Skipping LOCKED_BOOT_COMPLETED for private profile user #" + userId);
            return;
        }

        String[] packages = mInjector.getContext().getResources().getStringArray(
                R.array.config_privateSpaceBootCompletedImmediateReceivers);

        AtomicInteger remainingPackages = new AtomicInteger(packages.length);
        for (String packageName : packages) {
            Intent intent = new Intent(bootIntent);
            intent.setPackage(packageName);
            IIntentReceiver packageReceiver = new IIntentReceiver.Stub() {
                @Override
                public void performReceive(Intent intent, int resultCode, String data,
                        Bundle extras, boolean ordered, boolean sticky, int sendingUser)
                        throws RemoteException {
                    if (remainingPackages.decrementAndGet() == 0 && !Objects.isNull(receiver)) {
                        receiver.performReceive(intent, resultCode, data, extras, ordered, sticky,
                                sendingUser);
                    }
                }
            };
            mInjector.broadcastIntent(intent, null, packageReceiver, 0, null, null,
                    new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
                    AppOpsManager.OP_NONE, getTemporaryAppAllowlistBroadcastOptions(
                            REASON_LOCKED_BOOT_COMPLETED).toBundle(), false, MY_PID, SYSTEM_UID,
                    Binder.getCallingUid(), Binder.getCallingPid(), userId);
        }
    }

    /**
     * Step from {@link UserState#STATE_RUNNING_LOCKED} to
     * {@link UserState#STATE_RUNNING_UNLOCKING}.
     */
    private boolean finishUserUnlocking(final UserState uss) {
        final int userId = uss.mHandle.getIdentifier();
        EventLog.writeEvent(EventLogTags.UC_FINISH_USER_UNLOCKING, userId);
        mInjector.getUserJourneyLogger()
                .logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_UNLOCKING_USER,
                EVENT_STATE_BEGIN);
        // If the user's CE storage hasn't been unlocked yet, we cannot proceed.
        if (!StorageManager.isCeStorageUnlocked(userId)) return false;
        synchronized (mLock) {
            // Do not proceed if unexpected state or a stale user
            if (mStartedUsers.get(userId) != uss || uss.state != STATE_RUNNING_LOCKED) {
                return false;
            }
        }
        uss.mUnlockProgress.start();

        // Prepare app storage before we go any further
        uss.mUnlockProgress.setProgress(5,
                    mInjector.getContext().getString(R.string.android_start_title));

        // Call onBeforeUnlockUser on a worker thread that allows disk I/O
        FgThread.getHandler().post(() -> {
            if (!StorageManager.isCeStorageUnlocked(userId)) {
                Slogf.w(TAG, "User's CE storage got locked unexpectedly, leaving user locked.");
                return;
            }

            final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
            t.traceBegin("UM.onBeforeUnlockUser-" + userId);
            mInjector.getUserManager().onBeforeUnlockUser(userId);
            t.traceEnd();
            synchronized (mLock) {
                // Do not proceed if unexpected state
                if (!uss.setState(STATE_RUNNING_LOCKED, STATE_RUNNING_UNLOCKING)) {
                    return;
                }
            }
            mInjector.getUserManagerInternal().setUserState(userId, uss.state);

            uss.mUnlockProgress.setProgress(20);

            mLastUserUnlockingUptime = SystemClock.uptimeMillis();

            // Dispatch unlocked to system services; when fully dispatched,
            // that calls through to the next "unlocked" phase
            mHandler.obtainMessage(USER_UNLOCK_MSG, userId, 0, uss).sendToTarget();
        });
        return true;
    }

    /**
     * Step from {@link UserState#STATE_RUNNING_UNLOCKING} to
     * {@link UserState#STATE_RUNNING_UNLOCKED}.
     */
    private void finishUserUnlocked(final UserState uss) {
        final int userId = uss.mHandle.getIdentifier();
        EventLog.writeEvent(EventLogTags.UC_FINISH_USER_UNLOCKED, userId);
        // Only keep marching forward if the user's CE storage is unlocked.
        if (!StorageManager.isCeStorageUnlocked(userId)) return;
        synchronized (mLock) {
            // Bail if we ended up with a stale user
            if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return;

            // Do not proceed if unexpected state
            if (!uss.setState(STATE_RUNNING_UNLOCKING, STATE_RUNNING_UNLOCKED)) {
                return;
            }
        }
        mInjector.getUserManagerInternal().setUserState(userId, uss.state);
        uss.mUnlockProgress.finish();

        // Get unaware persistent apps running and start any unaware providers
        // in already-running apps that are partially aware
        if (userId == UserHandle.USER_SYSTEM) {
            mInjector.startPersistentApps(PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
        }
        mInjector.installEncryptionUnawareProviders(userId);

        if (!mInjector.getUserManager().isPreCreated(userId)) {
            // Dispatch unlocked to external apps
            final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED);
            unlockedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
            unlockedIntent.addFlags(
                    Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
            mInjector.broadcastIntent(unlockedIntent, null, null, 0, null,
                    null, null, AppOpsManager.OP_NONE, null, false, MY_PID, SYSTEM_UID,
                    Binder.getCallingUid(), Binder.getCallingPid(), userId);
        }

        final UserInfo userInfo = getUserInfo(userId);
        if (userInfo.isProfile()) {
            UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
            if (parent != null) {
                // Send PROFILE_ACCESSIBLE broadcast to the parent user if a profile was unlocked
                broadcastProfileAccessibleStateChanged(userId, parent.id,
                        Intent.ACTION_PROFILE_ACCESSIBLE);

                //TODO(b/175704931): send ACTION_MANAGED_PROFILE_AVAILABLE

                // Also send MANAGED_PROFILE_UNLOCKED broadcast to the parent user
                // if a managed profile was unlocked
                if (userInfo.isManagedProfile()) {
                    final Intent profileUnlockedIntent = new Intent(
                            Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
                    profileUnlockedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
                    profileUnlockedIntent.addFlags(
                            Intent.FLAG_RECEIVER_REGISTERED_ONLY
                                    | Intent.FLAG_RECEIVER_FOREGROUND);
                    mInjector.broadcastIntent(profileUnlockedIntent,
                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                            null, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
                            Binder.getCallingPid(), parent.id);
                }
            }
        }

        // Send PRE_BOOT broadcasts if user fingerprint changed; we
        // purposefully block sending BOOT_COMPLETED until after all
        // PRE_BOOT receivers are finished to avoid ANR'ing apps
        final UserInfo info = getUserInfo(userId);
        if (!Objects.equals(info.lastLoggedInFingerprint, PackagePartitions.FINGERPRINT)
                || SystemProperties.getBoolean("persist.pm.mock-upgrade", false)) {
            // Suppress double notifications for managed profiles that
            // were unlocked automatically as part of their parent user being
            // unlocked.  TODO(b/217442918): this code doesn't work correctly.
            final boolean quiet = info.isManagedProfile();
            mInjector.sendPreBootBroadcast(userId, quiet,
                    () -> finishUserUnlockedCompleted(uss));
        } else {
            finishUserUnlockedCompleted(uss);
        }
    }

    private void finishUserUnlockedCompleted(UserState uss) {
        final int userId = uss.mHandle.getIdentifier();
        EventLog.writeEvent(EventLogTags.UC_FINISH_USER_UNLOCKED_COMPLETED, userId);
        synchronized (mLock) {
            // Bail if we ended up with a stale user
            if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return;
        }
        UserInfo userInfo = getUserInfo(userId);
        if (userInfo == null) {
            return;
        }
        // Only keep marching forward if the user's CE storage is unlocked.
        if (!StorageManager.isCeStorageUnlocked(userId)) return;

        // Remember that we logged in
        mInjector.getUserManager().onUserLoggedIn(userId);

        Runnable initializeUser = () -> mInjector.getUserManager().makeInitialized(userInfo.id);
        if (!userInfo.isInitialized()) {
            Slogf.d(TAG, "Initializing user #" + userId);
            if (userInfo.preCreated) {
                initializeUser.run();
            } else if (userId != UserHandle.USER_SYSTEM) {
                Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE);
                intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
                        | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
                mInjector.broadcastIntent(intent, null,
                        new IIntentReceiver.Stub() {
                            @Override
                            public void performReceive(Intent intent, int resultCode,
                                    String data, Bundle extras, boolean ordered,
                                    boolean sticky, int sendingUser) {
                                // Note: performReceive is called with mService lock held
                                initializeUser.run();
                            }
                        }, 0, null, null, null, AppOpsManager.OP_NONE,
                        null, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
                        Binder.getCallingPid(), userId);
            }
        }

        if (userInfo.preCreated) {
            Slogf.i(TAG, "Stopping pre-created user " + userInfo.toFullString());
            // Pre-created user was started right after creation so services could properly
            // intialize it; it should be stopped right away as it's not really a "real" user.
            stopUser(userInfo.id, /* allowDelayedLocking= */ false,
                    /* stopUserCallback= */ null, /* keyEvictedCallback= */ null);
            return;
        }

        // Spin up app widgets prior to boot-complete, so they can be ready promptly
        mInjector.startUserWidgets(userId);

        mHandler.obtainMessage(USER_UNLOCKED_MSG, userId, 0).sendToTarget();

        Slogf.i(TAG, "Posting BOOT_COMPLETED user #" + userId);
        // Do not report secondary users, runtime restarts or first boot/upgrade
        if (userId == UserHandle.USER_SYSTEM
                && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
            final long elapsedTimeMs = SystemClock.elapsedRealtime();
            FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
                    FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__FRAMEWORK_BOOT_COMPLETED,
                    elapsedTimeMs);
        }
        final Intent bootIntent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
        bootIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
        bootIntent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
                | Intent.FLAG_RECEIVER_OFFLOAD);
        // Widget broadcasts are outbound via FgThread, so to guarantee sequencing
        // we also send the boot_completed broadcast from that thread.
        final int callingUid = Binder.getCallingUid();
        final int callingPid = Binder.getCallingPid();

        if (userInfo.isPrivateProfile()) {
            sendBroadcastBootCompleteForPrivateProfileApps(bootIntent, userId, callingUid,
                    callingPid);
            return;
        }
        FgThread.getHandler().post(() -> {
            broadcastBootCompletedIntent(bootIntent, userId, callingUid, callingPid);
        });
    }

    /**
     * Only broadcast the BOOT_COMPLETED intent to allowlisted immediate receivers. Other packages
     * will receive the intent after they are started.
     */
    private void sendBroadcastBootCompleteForPrivateProfileApps(Intent bootIntent, int userId,
            int callingUid, int callingPid) {
        if (!android.multiuser.Flags.enableMovingContentIntoPrivateSpace()) {
            Slogf.i(TAG, "Skipping BOOT_COMPLETED for private profile user #" + userId);
            return;
        }

        FgThread.getHandler().post(() -> {
            String[] packages = mInjector.getContext().getResources().getStringArray(
                    R.array.config_privateSpaceBootCompletedImmediateReceivers);

            for (String packageName : packages) {
                Intent intent = new Intent(bootIntent);
                intent.setPackage(packageName);
                broadcastBootCompletedIntent(intent, userId, callingUid, callingPid);
            }
        });
    }

    private void broadcastBootCompletedIntent(Intent bootIntent, int userId, int callingUid,
            int callingPid) {
        mInjector.broadcastIntent(bootIntent, null,
                new IIntentReceiver.Stub() {
                    @Override
                    public void performReceive(Intent intent, int resultCode, String data,
                            Bundle extras, boolean ordered, boolean sticky, int sendingUser)
                            throws RemoteException {
                        Slogf.i(UserController.TAG,
                                "Finished processing BOOT_COMPLETED for u" + userId
                                        + (Objects.isNull(bootIntent.getPackage()) ? ""
                                        : ", package: " + bootIntent.getPackage()));
                    }
                }, 0, null, null,
                new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
                AppOpsManager.OP_NONE,
                getTemporaryAppAllowlistBroadcastOptions(REASON_BOOT_COMPLETED).toBundle(),
                false, MY_PID, SYSTEM_UID, callingUid, callingPid, userId);
    }

    /**
     * Initiates the logout process for the specified user.
     *
     * <p>The logout process involves two main steps:
     *
     * <ol>
     *   <li>If {@code userId} is the foreground user, first switching to an appropriate, active
     *       user. Currently it's always switching to the system user.
     *   <li>Stopping the specified {@code userId}.
     * </ol>
     *
     * <p>The logout operation will fail under the following conditions:
     *
     * <ul>
     *   <li>Trying to logout the system user.
     *   <li>Device cannot switch to the system user.
     *   <li>The user switch operation fails for any reason.
     *   <li>The stop user operation fails for any reason.
     * </ul>
     *
     * @see ActivityManager#logoutUser(int)
     * @param userId The ID of the user to log out.
     * @return true if logout is successfully initiated.
     * @throws UnsupportedOperationException if device is in HSUM and cannot switch to system user.
     */
    boolean logoutUser(@UserIdInt int userId) {
        // TODO(b/380125011): To handle the edge case of trying to logout the user that the device
        // is in the process of switching to (i.e. userId == mTargetUserId), we had to logout to the
        // system user (not the previous foreground user). Thus we cannot support HSUM devices
        // without interactive headless system user. To solve it for the long term, a refactor might
        // be needed, see more details in the bug.
        if (!mInjector.doesUserSupportSwitchTo(getUserInfo(UserHandle.USER_SYSTEM))) {
            throw new UnsupportedOperationException("device does not support logoutUser");
        }
        boolean shouldSwitchUser = false;
        synchronized (mLock) {
            if (userId == UserHandle.USER_SYSTEM) {
                Slogf.e(TAG, "Cannot logout system user %d", userId);
                return false;
            }
            // Since we are logging out of userId, let's not switch to userId (after possible
            // ongoing user switch).
            mPendingTargetUserIds.removeIf(id -> id == userId);

            if (userId == mTargetUserId) {
                Slogf.w(TAG, "Cannot logout user %d now as we're in the process of switching to it,"
                        + " it will be logged out when the ongoing user-switch is complete.",
                        userId);
                mPendingLogoutTargetUser = true;
                return true;
            }
            if (userId == getCurrentUserId() && mTargetUserId == UserHandle.USER_NULL) {
                shouldSwitchUser = true;
            }
        }

        if (shouldSwitchUser) {
            if (!switchUser(UserHandle.USER_SYSTEM)) {
                Slogf.e(TAG, "Cannot logout user %d; switch to system user failed", userId);
                return false;
            }
        }
        // Now, userId is not current, so we can simply stop it.
        final int result = stopUser(userId, /* allowDelayedLocking= */ false,
                /* stopUserCallback= */ null, /* keyEvictedCallback */ null);
        if (result != USER_OP_SUCCESS) {
            Slogf.e(TAG, "Cannot logout user %d; stop user failed with result %d", userId, result);
            return false;
        }
        return true;
    }

    int restartUser(final int userId, @UserStartMode int userStartMode) {
        return stopUser(userId, /* allowDelayedLocking= */ false,
                /* stopUserCallback= */ null, new KeyEvictedCallback() {
                    @Override
                    public void keyEvicted(@UserIdInt int userId) {
                        // Post to the same handler that this callback is called from to ensure
                        // the user cleanup is complete before restarting.
                        mHandler.post(() -> UserController.this.startUser(userId, userStartMode));
                    }
                });
    }

    /**
     * Stops a user only if it's a profile, with a more relaxed permission requirement:
     * {@link android.Manifest.permission#MANAGE_USERS} or
     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}.
     * To be called from ActivityManagerService.
     * @param userId the id of the user to stop.
     * @return true if the operation was successful.
     */
    boolean stopProfile(final @UserIdInt int userId) {
        if (mInjector.checkCallingPermission(android.Manifest.permission.MANAGE_USERS)
                == PackageManager.PERMISSION_DENIED && mInjector.checkCallingPermission(
                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
                == PackageManager.PERMISSION_DENIED) {
            throw new SecurityException(
                    "You either need MANAGE_USERS or INTERACT_ACROSS_USERS_FULL permission to "
                            + "stop a profile");
        }

        final UserInfo userInfo = getUserInfo(userId);
        if (userInfo == null || !userInfo.isProfile()) {
            throw new IllegalArgumentException("User " + userId + " is not a profile");
        }

        enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
        synchronized (mLock) {
            return stopUsersLU(userId, /* allowDelayedLocking= */ false,
                    /* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
                    == ActivityManager.USER_OP_SUCCESS;
        }
    }

    int stopUser(final int userId, boolean allowDelayedLocking,
            final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
        return stopUser(userId, true, allowDelayedLocking, stopUserCallback, keyEvictedCallback);
    }

    int stopUser(final int userId,
            final boolean stopProfileRegardlessOfParent, final boolean allowDelayedLocking,
            final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
        TimingsTraceAndSlog t = new TimingsTraceAndSlog();

        t.traceBegin("UserController"
                + (stopProfileRegardlessOfParent ? "-stopProfileRegardlessOfParent" : "")
                + (allowDelayedLocking ? "-allowDelayedLocking" : "")
                + (stopUserCallback != null ? "-withStopUserCallback" : "")
                + "-" + userId + "-[stopUser]");
        try {
            checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "stopUser");
            Preconditions.checkArgument(userId >= 0, "Invalid user id %d", userId);

            enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
            synchronized (mLock) {
                return stopUsersLU(userId, stopProfileRegardlessOfParent, allowDelayedLocking,
                        stopUserCallback, keyEvictedCallback);
            }
        } finally {
            t.traceEnd();
        }
    }

    /** Stops the user along with its profiles. */
    @GuardedBy("mLock")
    private int stopUsersLU(final int userId, boolean allowDelayedLocking,
            final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
        return stopUsersLU(userId, /* stopProfileRegardlessOfParent= */ true,
                allowDelayedLocking, stopUserCallback, keyEvictedCallback);
    }

    /**
     * Stops the user along with its profiles. The method calls
     * {@link #getUsersToStopLU(int)} to determine the list of users that should be stopped.
     *
     * @param stopProfileRegardlessOfParent whether to stop the profile regardless of who its
     *                                      parent is, e.g. even if the parent is the current user
     */
    @GuardedBy("mLock")
    private int stopUsersLU(final int userId,
            boolean stopProfileRegardlessOfParent, boolean allowDelayedLocking,
            final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
        if (userId == UserHandle.USER_SYSTEM) {
            return USER_OP_ERROR_IS_SYSTEM;
        }
        if (isCurrentUserLU(userId)) {
            return USER_OP_IS_CURRENT;
        }
        if (!stopProfileRegardlessOfParent) {
            final int parentId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
            if (parentId != UserInfo.NO_PROFILE_GROUP_ID && parentId != userId) {
                if ((!android.multiuser.Flags.stopExcessForBackgroundStarts()
                        && UserHandle.USER_SYSTEM == parentId)
                        || isCurrentUserLU(parentId)) {
                    return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
                }
            }
        }
        final int[] usersToStop = getUsersToStopLU(userId);

        // Final safety check: abort if one of the users we would plan to stop must not be stopped.
        // This should be impossible in the current code, but just in case.
        for (int relatedUserId : usersToStop) {
            if ((UserHandle.USER_SYSTEM == relatedUserId) || isCurrentUserLU(relatedUserId)) {
                Slogf.e(TAG, "Cannot stop user %d because it is related to user %d. ",
                        userId, relatedUserId);
                return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
            }
        }

        TimingsTraceAndSlog t = new TimingsTraceAndSlog();
        if (DEBUG_MU) Slogf.i(TAG, "stopUsersLocked usersToStop=" + Arrays.toString(usersToStop));
        for (int userIdToStop : usersToStop) {
            t.traceBegin("stopSingleUserLU-" + userIdToStop + "-[stopUser]");
            stopSingleUserLU(userIdToStop, allowDelayedLocking,
                    userIdToStop == userId ? stopUserCallback : null,
                    userIdToStop == userId ? keyEvictedCallback : null);
            t.traceEnd();
        }
        return USER_OP_SUCCESS;
    }

    /**
     * Stops a single User.
     *
     * This should only ever be called by {@link #stopUsersLU},
     * which is responsible to making sure any associated users are appropriately stopped too.
     *
     * This can also trigger locking user data out depending on device's
     * config ({@code mDelayUserDataLocking}) and arguments.
     *
     * In the default configuration for most device and users, users will be locked when stopping.
     * User will remain unlocked only if all the following are true
     * <li> {@link #canDelayDataLockingForUser(int)} (based on mDelayUserDataLocking) is true
     * <li> the parameter {@code allowDelayedLocking} is true
     * <li> {@code keyEvictedCallback} is null
     * -
     *
     * @param userId User Id to stop and lock the data.
     * @param allowDelayedLocking When set, do not lock user after stopping. Locking can happen
     *                            later when number of unlocked users reaches
     *                            {@code mMaxRunningUsers}. Note that this is respected only when
     *                            delayed locking is enabled for this user and {@keyEvictedCallback}
     *                            is null. Otherwise the user nonetheless will be locked.
     * @param stopUserCallback Callback to notify that user has stopped.
     * @param keyEvictedCallback Callback to notify that user has been unlocked.
     */
    @GuardedBy("mLock")
    private void stopSingleUserLU(final int userId, boolean allowDelayedLocking,
            final IStopUserCallback stopUserCallback,
            KeyEvictedCallback keyEvictedCallback) {
        Slogf.i(TAG, "stopSingleUserLU userId=" + userId);
        clearAnyPlansForStoppingBackgroundUser(userId);
        final UserState uss = mStartedUsers.get(userId);
        if (uss == null) {  // User is not started
            // If canDelayDataLockingForUser() is true and allowDelayedLocking is false, we need
            // to lock the requested user as the client wants to stop and lock the user. On the
            // other hand, having keyEvictedCallback set will lead into locking user if
            // canDelayDataLockingForUser() is true as that means client wants to lock the user
            // immediately.
            // If canDelayDataLockingForUser() is false, the user was already locked when it was
            // stopped and no further action is necessary.
            if (canDelayDataLockingForUser(userId)) {
                if (allowDelayedLocking && keyEvictedCallback != null) {
                    Slogf.wtf(TAG, "allowDelayedLocking set with KeyEvictedCallback, ignore it"
                            + " and lock user:" + userId, new RuntimeException());
                    allowDelayedLocking = false;
                }
                if (!allowDelayedLocking) {
                    if (mLastActiveUsersForDelayedLocking.remove(Integer.valueOf(userId))) {
                        // should lock the user, user is already gone
                        final ArrayList<KeyEvictedCallback> keyEvictedCallbacks;
                        if (keyEvictedCallback != null) {
                            keyEvictedCallbacks = new ArrayList<>(1);
                            keyEvictedCallbacks.add(keyEvictedCallback);
                        } else {
                            keyEvictedCallbacks = null;
                        }
                        dispatchUserLocking(userId, keyEvictedCallbacks);
                    }
                }
            }
            // We do need to post the stopped callback even though user is already stopped.
            if (stopUserCallback != null) {
                mHandler.post(() -> {
                    try {
                        stopUserCallback.userStopped(userId);
                    } catch (RemoteException e) {
                    }
                });
            }
            return;
        }

        logUserJourneyBegin(userId, USER_JOURNEY_USER_STOP);

        if (stopUserCallback != null) {
            uss.mStopCallbacks.add(stopUserCallback);
        }
        if (keyEvictedCallback != null) {
            uss.mKeyEvictedCallbacks.add(keyEvictedCallback);
        }

        if (uss.state != UserState.STATE_STOPPING
                && uss.state != UserState.STATE_SHUTDOWN) {
            uss.setState(UserState.STATE_STOPPING);
            UserManagerInternal userManagerInternal = mInjector.getUserManagerInternal();
            TimingsTraceAndSlog t = new TimingsTraceAndSlog();
            t.traceBegin("setUserState-STATE_STOPPING-" + userId + "-[stopUser]");
            userManagerInternal.setUserState(userId, uss.state);
            t.traceEnd();
            t.traceBegin("unassignUserFromDisplayOnStop-" + userId + "-[stopUser]");
            userManagerInternal.unassignUserFromDisplayOnStop(userId);
            t.traceEnd();

            updateStartedUserArrayLU();

            final boolean allowDelayedLockingCopied = allowDelayedLocking;
            Runnable finishUserStoppingAsync = () ->
                    mHandler.post(() -> {
                        TimingsTraceAndSlog t2 = new TimingsTraceAndSlog();
                        t2.traceBegin("finishUserStopping-" + userId + "-[stopUser]");
                        finishUserStopping(userId, uss, allowDelayedLockingCopied);
                        t2.traceEnd();
                    });

            if (mInjector.getUserManager().isPreCreated(userId)) {
                finishUserStoppingAsync.run();
                return;
            }

            // Post to handler to obtain amLock
            mHandler.post(() -> {
                // We are going to broadcast ACTION_USER_STOPPING and then
                // once that is done send a final ACTION_SHUTDOWN and then
                // stop the user.
                final Intent stoppingIntent = new Intent(Intent.ACTION_USER_STOPPING);
                stoppingIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
                stoppingIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
                stoppingIntent.putExtra(Intent.EXTRA_SHUTDOWN_USERSPACE_ONLY, true);
                // This is the result receiver for the initial stopping broadcast.
                final IIntentReceiver stoppingReceiver = new IIntentReceiver.Stub() {
                    @Override
                    public void performReceive(Intent intent, int resultCode, String data,
                            Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
                        asyncTraceEnd("broadcast-ACTION_USER_STOPPING-" + userId + "-[stopUser]",
                                userId);
                        finishUserStoppingAsync.run();
                    }
                };

                TimingsTraceAndSlog t2 = new TimingsTraceAndSlog();
                t2.traceBegin("clearBroadcastQueueForUser-" + userId + "-[stopUser]");
                // Clear broadcast queue for the user to avoid delivering stale broadcasts
                mInjector.clearBroadcastQueueForUser(userId);
                t2.traceEnd();
                asyncTraceBegin("broadcast-ACTION_USER_STOPPING-" + userId + "-[stopUser]", userId);
                // Kick things off.
                mInjector.broadcastIntent(stoppingIntent,
                        null, stoppingReceiver, 0, null, null,
                        new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
                        null, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
                        Binder.getCallingPid(), UserHandle.USER_ALL);
            });
        }
    }

    private void finishUserStopping(final int userId, final UserState uss,
            final boolean allowDelayedLocking) {
        final UserInfo userInfo = getUserInfo(userId);
        EventLog.writeEvent(EventLogTags.UC_FINISH_USER_STOPPING, userId);
        synchronized (mLock) {
            if (uss.state != UserState.STATE_STOPPING) {
                // Whoops, we are being started back up.  Abort, abort!
                UserJourneySession session = mInjector.getUserJourneyLogger()
                        .logUserJourneyFinishWithError(-1, userInfo,
                                USER_JOURNEY_USER_STOP, ERROR_CODE_ABORTED);
                if (session != null) {
                    mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, session);
                } else {
                    mInjector.getUserJourneyLogger()
                            .logUserJourneyFinishWithError(-1, userInfo,
                                    USER_JOURNEY_USER_STOP, ERROR_CODE_INVALID_SESSION_ID);
                }
                return;
            }
            uss.setState(UserState.STATE_SHUTDOWN);
            mDoNotAbortShutdownUserIds.remove(userId);
        }
        TimingsTraceAndSlog t = new TimingsTraceAndSlog();
        t.traceBegin("setUserState-STATE_SHUTDOWN-" + userId + "-[stopUser]");
        mInjector.getUserManagerInternal().setUserState(userId, uss.state);
        t.traceEnd();

        mInjector.batteryStatsServiceNoteEvent(
                BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
                Integer.toString(userId), userId);
        mInjector.getSystemServiceManager().onUserStopping(userId);

        Runnable finishUserStoppedAsync = () ->
                mHandler.post(() -> {
                    TimingsTraceAndSlog t2 = new TimingsTraceAndSlog();
                    t2.traceBegin("finishUserStopped-" + userId + "-[stopUser]");
                    finishUserStopped(uss, allowDelayedLocking);
                    t2.traceEnd();
                });
        if (mInjector.getUserManager().isPreCreated(userId)) {
            finishUserStoppedAsync.run();
            return;
        }

        // Fire the shutdown intent.
        final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
        // This is the result receiver for the final shutdown broadcast.
        final IIntentReceiver shutdownReceiver = new IIntentReceiver.Stub() {
            @Override
            public void performReceive(Intent intent, int resultCode, String data,
                    Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
                asyncTraceEnd("broadcast-ACTION_SHUTDOWN-" + userId + "-[stopUser]", userId);
                finishUserStoppedAsync.run();
            }
        };
        asyncTraceBegin("broadcast-ACTION_SHUTDOWN-" + userId + "-[stopUser]", userId);
        mInjector.broadcastIntent(shutdownIntent,
                null, shutdownReceiver, 0, null, null, null,
                AppOpsManager.OP_NONE,
                null, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
                Binder.getCallingPid(), userId);
    }

    @VisibleForTesting
    void finishUserStopped(UserState uss, boolean allowDelayedLocking) {
        final int userId = uss.mHandle.getIdentifier();
        if (DEBUG_MU) {
            Slogf.i(TAG, "finishUserStopped(%d): allowDelayedLocking=%b", userId,
                    allowDelayedLocking);
        }

        EventLog.writeEvent(EventLogTags.UC_FINISH_USER_STOPPED, userId);
        final boolean stopped;
        boolean lockUser = true;
        final ArrayList<IStopUserCallback> stopCallbacks;
        final ArrayList<KeyEvictedCallback> keyEvictedCallbacks;
        int userIdToLock = userId;
        // Must get a reference to UserInfo before it's removed
        final UserInfo userInfo = getUserInfo(userId);
        synchronized (mLock) {
            stopCallbacks = new ArrayList<>(uss.mStopCallbacks);
            keyEvictedCallbacks = new ArrayList<>(uss.mKeyEvictedCallbacks);
            if (mStartedUsers.get(userId) != uss || uss.state != UserState.STATE_SHUTDOWN) {
                stopped = false;
            } else {
                stopped = true;
                // User can no longer run.
                Slogf.i(TAG, "Removing user state from UserController.mStartedUsers for user #"
                        + userId + " as a result of user being stopped");
                mStartedUsers.remove(userId);

                mUserLru.remove(Integer.valueOf(userId));
                updateStartedUserArrayLU();

                if (allowDelayedLocking && !keyEvictedCallbacks.isEmpty()) {
                    Slogf.wtf(TAG,
                            "Delayed locking enabled while KeyEvictedCallbacks not empty, userId:"
                                    + userId + " callbacks:" + keyEvictedCallbacks);
                    allowDelayedLocking = false;
                }
                userIdToLock = updateUserToLockLU(userInfo, allowDelayedLocking);
                if (userIdToLock == UserHandle.USER_NULL) {
                    lockUser = false;
                }
            }
        }
        TimingsTraceAndSlog t = new TimingsTraceAndSlog();
        if (stopped) {
            Slogf.i(TAG, "Removing user state from UserManager.mUserStates for user #" + userId
                    + " as a result of user being stopped");
            mInjector.getUserManagerInternal().removeUserState(userId);

            mInjector.activityManagerOnUserStopped(userId);
            // Clean up all state and processes associated with the user.
            // Kill all the processes for the user.
            t.traceBegin("stopPackagesOfStoppedUser-" + userId + "-[stopUser]");
            stopPackagesOfStoppedUser(userId, "finish user");
            t.traceEnd();
        }

        for (final IStopUserCallback callback : stopCallbacks) {
            try {
                if (stopped) {
                    t.traceBegin("stopCallbacks.userStopped-" + userId + "-[stopUser]");
                    callback.userStopped(userId);
                    t.traceEnd();
                } else {
                    t.traceBegin("stopCallbacks.userStopAborted-" + userId + "-[stopUser]");
                    callback.userStopAborted(userId);
                    t.traceEnd();
                }
            } catch (RemoteException ignored) {
            }
        }

        if (stopped) {
            t.traceBegin("systemServiceManagerOnUserStopped-" + userId + "-[stopUser]");
            mInjector.systemServiceManagerOnUserStopped(userId);
            t.traceEnd();
            t.traceBegin("taskSupervisorRemoveUser-" + userId + "-[stopUser]");
            mInjector.taskSupervisorRemoveUser(userId);
            t.traceEnd();

            // Remove the user if it is ephemeral.
            if (userInfo.isEphemeral() && !userInfo.preCreated) {
                mInjector.getUserManager().removeUserEvenWhenDisallowed(userId);
            }

            UserJourneySession session = mInjector.getUserJourneyLogger()
                    .logUserJourneyFinish(-1, userInfo, USER_JOURNEY_USER_STOP);
            if (session != null) {
                mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, session);
            }

            if (lockUser) {
                dispatchUserLocking(userIdToLock, keyEvictedCallbacks);
            }

            // Resume any existing pending user start,
            // which was paused while the SHUTDOWN flow of the user was in progress.
            resumePendingUserStarts(userId);
        } else {
            UserJourneySession session = mInjector.getUserJourneyLogger()
                    .finishAndClearIncompleteUserJourney(userId, USER_JOURNEY_USER_STOP);
            if (session != null) {
                mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, session);
            }
        }
    }

    /**
     * Resume any existing pending user start for the specified userId which was paused
     * while the shutdown flow of the user was in progress.
     * Remove all the handled user starts from mPendingUserStarts.
     * @param userId the id of the user
     */
    private void resumePendingUserStarts(@UserIdInt int userId) {
        synchronized (mLock) {
            final List<PendingUserStart> handledUserStarts = new ArrayList<>();

            for (PendingUserStart userStart: mPendingUserStarts) {
                if (userStart.userId == userId) {
                    Slogf.i(TAG, "resumePendingUserStart for" + userStart);
                    // TODO(b/416746742): Don't we need to track the displayId, not use the DEFAULT?
                    mHandler.post(() -> startUserNoChecks(userStart.userId, Display.DEFAULT_DISPLAY,
                            userStart.userStartMode, userStart.autoStopUserInSecs,
                            userStart.unlockListener));

                    handledUserStarts.add(userStart);
                }
            }
            // remove all the pending user starts which are now handled
            mPendingUserStarts.removeAll(handledUserStarts);
        }
    }

    private void dispatchUserLocking(@UserIdInt int userId,
            @Nullable List<KeyEvictedCallback> keyEvictedCallbacks) {
        // Evict user secrets that require strong authentication to unlock. This includes locking
        // the user's credential-encrypted storage and evicting the user's keystore super keys.
        // Performed on FgThread to make it serialized with call to
        // UserManagerService.onBeforeUnlockUser in finishUserUnlocking to prevent data corruption.
        FgThread.getHandler().post(() -> {
            if (hasStartedUserState(userId)) {
                Slogf.w(TAG, "User was restarted, skipping key eviction");
                return;
            }
            mInjector.getLockSettingsInternal().lockUser(userId);

            if (keyEvictedCallbacks == null) {
                return;
            }
            for (int i = 0; i < keyEvictedCallbacks.size(); i++) {
                keyEvictedCallbacks.get(i).keyEvicted(userId);
            }
        });
    }

    /**
     * Returns which user, if any, should be locked when the given user is stopped.
     *
     * For typical (non-mDelayUserDataLocking) devices and users, this will be the provided user.
     *
     * However, for some devices or users (based on {@link #canDelayDataLockingForUser(int)}),
     * storage once unlocked is kept unlocked, even after the user is stopped, so the user to be
     * locked (if any) may differ.
     *
     * For mDelayUserDataLocking devices, the total number of unlocked user storage is limited
     * (currently by mMaxRunningUsers). If there are more unlocked users, evict and lock the least
     * recently stopped user and lock that user's data.
     *
     * Regardless of the mode, ephemeral user is always locked immediately.
     *
     * @return user id to lock. UserHandler.USER_NULL will be returned if no user should be locked.
     */
    @GuardedBy("mLock")
    private @CanBeNULL int updateUserToLockLU(UserInfo userInfo, boolean allowDelayedLocking) {
        final int userId = userInfo.id;
        if (!canDelayDataLockingForUser(userId)
                || !allowDelayedLocking
                || userInfo.isEphemeral()
                || hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) {
            return userId;
        }

        // Once we reach here, we are in a delayed locking scenario.
        // Now, no user will be locked, unless the device's policy dictates we should based on the
        // maximum of such users allowed for the device.
        // DOWNSTREAM change: to allow user data storage locking at a later point,
        // always add last active users for delayed locking, to be fetched if stopUser.
        // arg should be object, not index
        mLastActiveUsersForDelayedLocking.remove((Integer) userId);
        mLastActiveUsersForDelayedLocking.add(0, userId);
        if (mDelayUserDataLocking) {
            int totalUnlockedUsers = mStartedUsers.size()
                    + mLastActiveUsersForDelayedLocking.size();
            // The delayed locking determination is tangled with mMaxRunningUsers. This is
            // presumably because the primary point of leaving users unlocked via
            // mDelayUserDataLocking is so that we can bulk restart them all later. We don't want to
            // try restarting more of these users than mMaxRunningUsers allows, so we don't leave
            // more than this many users unlocked.
            if (totalUnlockedUsers > mMaxRunningUsers) { // should lock a user
                final int userIdToLock = mLastActiveUsersForDelayedLocking.get(
                        mLastActiveUsersForDelayedLocking.size() - 1);
                mLastActiveUsersForDelayedLocking
                        .remove(mLastActiveUsersForDelayedLocking.size() - 1);
                Slogf.i(TAG, "finishUserStopped: should stop user " + userId
                        + " but should lock user " + userIdToLock);
                return userIdToLock;
            }
        }
        Slogf.i(TAG, "finishUserStopped: should stop user " + userId + " but without any locking");
        return UserHandle.USER_NULL;
    }

    /**
     * Returns whether the user can have its CE storage left unlocked, even when it is stopped,
     * either due to a global device configuration or an individual user's property.
     */
    private boolean canDelayDataLockingForUser(@UserIdInt int userIdToLock) {
        if (UserControllerHelper.disallowDelayedLockingForUser(mInjector.getContext(), userIdToLock)) {
            Slogf.i(TAG, "Treating delayed data locking as false for userId=" + userIdToLock);
            return false;
        }

        if (allowBiometricUnlockForPrivateProfile()) {
            final UserProperties userProperties = getUserProperties(userIdToLock);
            return (mDelayUserDataLocking || (userProperties != null
                    && userProperties.getAllowStoppingUserWithDelayedLocking()));
        }
        return mDelayUserDataLocking;
    }

    private boolean allowBiometricUnlockForPrivateProfile() {
        return android.os.Flags.allowPrivateProfile()
                && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()
                && android.multiuser.Flags.enablePrivateSpaceFeatures();
    }

    /**
     * Determines the list of users that should be stopped together with the specified
     * {@code userId}, i.e. the user and its profiles (if the given user is a parent).
     * The returned list includes {@code userId}.
     */
    @GuardedBy("mLock")
    private @NonNull int[] getUsersToStopLU(@UserIdInt int userId) {
        int startedUsersSize = mStartedUsers.size();
        IntArray userIds = new IntArray();
        userIds.add(userId);
        int userGroupId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
        if (userGroupId == userId) {
            SparseIntArray delayedLockUsersToLock = new SparseIntArray();
            final int sizeOfDelayedStorageLocking = mLastActiveUsersForDelayedLocking.size();
            for (int i = sizeOfDelayedStorageLocking - 1; i >= 0; --i) {
                Integer userIdWithDelayedLocking = mLastActiveUsersForDelayedLocking.get(i);
                if (userIdWithDelayedLocking == null) {
                    continue;
                }
                int userIdWithDelayedLockingGroupId = mUserProfileGroupIds.get(
                        userIdWithDelayedLocking, UserInfo.NO_PROFILE_GROUP_ID);
                boolean sameGroup = (userGroupId != UserInfo.NO_PROFILE_GROUP_ID)
                        && (userGroupId == userIdWithDelayedLockingGroupId);
                // userId has already been added
                boolean sameUserId = userIdWithDelayedLocking == userId;
                if (!sameGroup || sameUserId) {
                    continue;
                }
                delayedLockUsersToLock.append(userIdWithDelayedLocking, 1);
            }
            // The user is the parent of the profile group. Stop its profiles too.
            for (int i = 0; i < startedUsersSize; i++) {
                UserState uss = mStartedUsers.valueAt(i);
                int startedUserId = uss.mHandle.getIdentifier();
                // Skip unrelated users (profileGroupId mismatch)
                int startedUserGroupId = mUserProfileGroupIds.get(startedUserId,
                        UserInfo.NO_PROFILE_GROUP_ID);
                boolean sameGroup = (userGroupId != UserInfo.NO_PROFILE_GROUP_ID)
                        && (userGroupId == startedUserGroupId);
                // userId has already been added
                boolean sameUserId = startedUserId == userId;
                if (!sameGroup || sameUserId) {
                    continue;
                }
                userIds.add(startedUserId);
                delayedLockUsersToLock.delete(startedUserId);
            }
            for (int j = delayedLockUsersToLock.size() - 1; j >= 0; --j) {
                int delayedLockUserId = delayedLockUsersToLock.keyAt(j);
                userIds.add(delayedLockUserId);
            }
        }
        return userIds.toArray();
    }

    private void stopPackagesOfStoppedUser(@UserIdInt int userId, String reason) {
        if (DEBUG_MU) Slogf.i(TAG, "stopPackagesOfStoppedUser(%d): %s", userId, reason);
        mInjector.activityManagerForceStopUserPackages(userId, reason,
                /* evenImportantServices= */ true);
        if (mInjector.getUserManager().isPreCreated(userId)) {
            // Don't fire intent for precreated.
            return;
        }
        Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                | Intent.FLAG_RECEIVER_FOREGROUND);
        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
        mInjector.broadcastIntent(intent,
                null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                null, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
                Binder.getCallingPid(), UserHandle.USER_ALL);

        // Send PROFILE_INACCESSIBLE broadcast if a profile was stopped
        final UserInfo userInfo = getUserInfo(userId);
        if (userInfo != null && userInfo.isProfile()) {
            UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
            if (parent != null) {
                broadcastProfileAccessibleStateChanged(userId, parent.id,
                        Intent.ACTION_PROFILE_INACCESSIBLE);
                //TODO(b/175704931): send ACTION_MANAGED_PROFILE_UNAVAILABLE
            }
        }
    }

    /**
     * Stops the guest or ephemeral user if it has gone to the background.
     */
    private void stopGuestOrEphemeralUserIfBackground(int oldUserId) {
        if (DEBUG_MU) Slogf.i(TAG, "Stop guest or ephemeral user if background: " + oldUserId);
        synchronized(mLock) {
            UserState oldUss = mStartedUsers.get(oldUserId);
            if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId || oldUss == null
                    || oldUss.state == UserState.STATE_STOPPING
                    || oldUss.state == UserState.STATE_SHUTDOWN) {
                return;
            }
        }

        UserInfo userInfo = getUserInfo(oldUserId);
        if (userInfo.isEphemeral()) {
            LocalServices.getService(UserManagerInternal.class).onEphemeralUserStop(oldUserId);
        }
        if (userInfo.isGuest() || userInfo.isEphemeral()) {
            // This is a user to be stopped.
            Slogf.i(TAG, "Stopping background guest or ephemeral user " + oldUserId);
            synchronized (mLock) {
                stopUsersLU(oldUserId, /* allowDelayedLocking= */ false, null, null);
            }
        }
    }

    private void stopPreviousUserPackagesIfEnabled(int fromUserId, int toUserId) {
        if (!android.multiuser.Flags.stopPreviousUserApps()
                || !isEarlyPackageKillEnabledForUserSwitch(fromUserId, toUserId)) {
            return;
        }
        // Stop the previous user's packages early to reduce resource usage
        // during user switching. Only do this when the previous user will
        // be stopped regardless.
        synchronized (mLock) {
            mDoNotAbortShutdownUserIds.add(fromUserId);
        }
        mInjector.activityManagerForceStopUserPackages(fromUserId,
                "early stop user packages", /* evenImportantServices= */ false);
    }

    void scheduleStartProfiles() {
        // Parent user transition to RUNNING_UNLOCKING happens on FgThread, so it is busy, there is
        // a chance the profile will reach RUNNING_LOCKED while parent is still locked, so no
        // attempt will be made to unlock the profile. If we go via FgThread, this will be executed
        // after the parent had chance to unlock fully.
        FgThread.getHandler().post(() -> {
            if (!mHandler.hasMessages(START_PROFILES_MSG)) {
                mHandler.sendMessageDelayed(mHandler.obtainMessage(START_PROFILES_MSG),
                        DateUtils.SECOND_IN_MILLIS);
            }
        });
    }

    /** Starts all applicable profiles of the current user. */
    private void startProfiles() {
        int currentUserId = getCurrentUserId();
        if (DEBUG_MU) Slogf.i(TAG, "startProfilesLocked");
        List<UserInfo> profiles = mInjector.getUserManager().getProfiles(
                currentUserId, false /* enabledOnly */);
        List<UserInfo> profilesToStart = new ArrayList<>(profiles.size());
        for (UserInfo user : profiles) {
            if ((user.flags & UserInfo.FLAG_INITIALIZED) == UserInfo.FLAG_INITIALIZED
                    && user.id != currentUserId
                    && shouldStartWithParent(user)) {
                profilesToStart.add(user);
            }
        }
        if (android.multiuser.Flags.stopExcessForBackgroundStarts()) {
            final int profilesToStartSize = profilesToStart.size();
            for (int i = 0; i < profilesToStartSize; ++i) {
                // NOTE: this method is setting the profiles of the current user - which is always
                // assigned to the default display
                startUser(profilesToStart.get(i).id, USER_START_MODE_BACKGROUND_VISIBLE);
            }
            final int maxRunningUsers = getMaxRunningUsers();
            if (profilesToStartSize >= maxRunningUsers) {
                Slogf.w(TAG, "User %d has more profiles to start (%d) than MAX_RUNNING_USERS would"
                        + " allow (%d)", currentUserId, profilesToStartSize, maxRunningUsers);
            }
        } else {
            final int profilesToStartSize = profilesToStart.size();
            int i = 0;
            for (; i < profilesToStartSize && i < (getMaxRunningUsers() - 1); ++i) {
                // NOTE: this method is setting the profiles of the current user - which is always
                // assigned to the default display
                startUser(profilesToStart.get(i).id, USER_START_MODE_BACKGROUND_VISIBLE);
            }
            if (i < profilesToStartSize) {
                Slogf.w(TAG, "More profiles than MAX_RUNNING_USERS");
            }
        }
    }

    private boolean shouldStartWithParent(UserInfo user) {
        final UserProperties properties = getUserProperties(user.id);
        return (properties != null && properties.getStartWithParent())
                && !user.isQuietModeEnabled();
    }

    /**
     * Starts a {@link UserManager#isProfile() profile user}.
     *
     * <p>To be called from {@link com.android.server.am.ActivityManagerService}.
     *
     * @param userId the id of the profile user to start.
     * @param evenWhenDisabled whether the profile should be started if it's not enabled yet
     *        (most callers should pass {@code false}, except when starting the profile while it's
     *        being provisioned).
     * @param unlockListener listener to be informed when the profile has started and unlocked.
     *
     * @return {@code true} if the operation was successful.
     *
     * @throws IllegalArgumentException if the user doesn't exist or is not a profile.
     */
    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
    boolean startProfile(@UserIdInt int userId, boolean evenWhenDisabled,
            @Nullable IProgressListener unlockListener) {
        if (mInjector.checkCallingPermission(android.Manifest.permission.MANAGE_USERS)
                == PackageManager.PERMISSION_DENIED && mInjector.checkCallingPermission(
                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
                == PackageManager.PERMISSION_DENIED) {
            throw new SecurityException(
                    "You either need MANAGE_USERS or INTERACT_ACROSS_USERS_FULL permission to "
                            + "start a profile");
        }

        final UserInfo userInfo = getUserInfo(userId);
        if (userInfo == null || !userInfo.isProfile()) {
            throw new IllegalArgumentException("User " + userId + " is not a profile");
        }

        if (!userInfo.isEnabled() && !evenWhenDisabled) {
            Slogf.w(TAG, "Cannot start disabled profile #%d", userId);
            return false;
        }

        return startUserNoChecks(userId, Display.DEFAULT_DISPLAY,
                USER_START_MODE_BACKGROUND_VISIBLE, unlockListener);
    }

    boolean startUserInBackgroundTemporarily(@UserIdInt int userId, int durationSecs) {
        synchronized (mLock) {
            if (!mReady) {
                return false;
            }
        }
        return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, USER_START_MODE_BACKGROUND,
                durationSecs, /* unlockListener= */ null);
    }

    @VisibleForTesting
    boolean startUser(@UserIdInt int userId, @UserStartMode int userStartMode) {
        return startUser(userId, userStartMode, /* unlockListener= */ null);
    }

    /**
     * Start user, if its not already running.
     *
     * <p>The user will be brought to the foreground, if {@code userStartMode} parameter is
     * set to {@link UserManagerInternal#USER_START_MODE_FOREGROUND}
     * When starting the user, multiple intents will be broadcast in the following order:</p>
     * <ul>
     *     <li>{@link Intent#ACTION_USER_STARTED} - sent to registered receivers of the new user
     *     <li>{@link Intent#ACTION_USER_BACKGROUND} - sent to registered receivers of the outgoing
     *     user and all profiles of this user. Sent only if {@code foreground} parameter is
     *     {@code false}
     *     <li>{@link Intent#ACTION_USER_FOREGROUND} - sent to registered receivers of the new
     *     user and all profiles of this user. Sent only if {@code foreground} parameter is
     *     {@code true}
     *     <li>{@link Intent#ACTION_USER_SWITCHED} - sent to registered receivers of the new user.
     *     Sent only if {@code foreground} parameter is {@code true}
     *     <li>{@link Intent#ACTION_USER_STARTING} - ordered broadcast sent to registered receivers
     *     of the new fg user
     *     <li>{@link Intent#ACTION_LOCKED_BOOT_COMPLETED} - ordered broadcast sent to receivers of
     *     the new user
     *     <li>{@link Intent#ACTION_USER_UNLOCKED} - sent to registered receivers of the new user
     *     <li>{@link Intent#ACTION_PRE_BOOT_COMPLETED} - ordered broadcast sent to receivers of the
     *     new user. Sent only when the user is booting after a system update.
     *     <li>{@link Intent#ACTION_USER_INITIALIZE} - ordered broadcast sent to receivers of the
     *     new user. Sent only the first time a user is starting.
     *     <li>{@link Intent#ACTION_BOOT_COMPLETED} - ordered broadcast sent to receivers of the new
     *     user. Indicates that the user has finished booting.
     * </ul>
     *
     * @param userId ID of the user to start
     * @param userStartMode user starting mode
     * @param unlockListener Listener to be informed when the user has started and unlocked.
     * @return true if the user has been successfully started
     */
    boolean startUser(@UserIdInt int userId, @UserStartMode int userStartMode,
            @Nullable IProgressListener unlockListener) {
        checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "startUser");

        return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, userStartMode, unlockListener);
    }

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

        try {
            return startUserNoChecks(userId, displayId, USER_START_MODE_BACKGROUND_VISIBLE,
                    unlockListener);
        } catch (RuntimeException e) {
            Slogf.e(TAG, "startUserOnSecondaryDisplay(%d, %d) failed: %s", userId, displayId, e);
            return false;
        }
    }

    private boolean startUserNoChecks(@UserIdInt int userId, int displayId,
            @UserStartMode int userStartMode, @Nullable IProgressListener unlockListener) {
        return startUserNoChecks(userId, displayId, userStartMode, -1, unlockListener);
    }

    private boolean startUserNoChecks(@UserIdInt int userId, int displayId,
            @UserStartMode int userStartMode, int autoStopUserInSecs,
            @Nullable IProgressListener unlockListener) {
        TimingsTraceAndSlog t = new TimingsTraceAndSlog();

        t.traceBegin("UserController.startUser-" + userId
                + (displayId == Display.DEFAULT_DISPLAY ? "" : "-display-" + displayId)
                + "-" + (userStartMode == USER_START_MODE_FOREGROUND ? "fg" : "bg")
                + (autoStopUserInSecs > 0 ? "-for-" + autoStopUserInSecs + "s" : "")
                + "-start-mode-" + userStartMode);
        try {
            return startUserInternal(userId, displayId, userStartMode, autoStopUserInSecs,
                    unlockListener, t);
        } finally {
            t.traceEnd();
        }
    }

    private boolean startUserInternal(@UserIdInt int userId, int displayId,
            @UserStartMode int userStartMode, int autoStopUserInSecs,
            @Nullable IProgressListener unlockListener, TimingsTraceAndSlog t) {
        synchronized (mLock) {
            if (DEBUG_MU) {
                Slogf.i(TAG, "Starting user %d on display %d with mode %s (when ready=%b)", userId,
                        displayId, userStartModeToString(userStartMode), mReady);
            }
            // NOTE: for now this is the only place that's mReady, but if it's needed in others,
            // this check should be encapsulated into a private helper.
            Preconditions.checkState(mReady, EXCEPTION_TEMPLATE_CANNOT_START_USER_WHEN_NOT_READY,
                    userId);
        }
        boolean foreground = userStartMode == USER_START_MODE_FOREGROUND;

        boolean onSecondaryDisplay = displayId != Display.DEFAULT_DISPLAY;
        if (onSecondaryDisplay) {
            Preconditions.checkArgument(!foreground, "Cannot start user %d in foreground AND "
                    + "on secondary display (%d)", userId, displayId);
        }
        if (autoStopUserInSecs > 0) {
            Preconditions.checkArgument(userStartMode == USER_START_MODE_BACKGROUND,
                    "Cannot auto-stop a non-bg (%d) user %d in %d s",
                    userStartMode, userId, autoStopUserInSecs);
        }
        EventLog.writeEvent(EventLogTags.UC_START_USER_INTERNAL, userId, foreground ? 1 : 0,
                displayId);

        final int callingUid = Binder.getCallingUid();
        final int callingPid = Binder.getCallingPid();
        final long ident = Binder.clearCallingIdentity();
        try {
            t.traceBegin("getStartedUserState");
            final int oldCurrentUserId = getCurrentUserId();
            if (oldCurrentUserId == userId) {
                // The user we're requested to start is already the current user.
                final UserState state = getStartedUserState(userId);
                if (state == null) {
                    Slogf.wtf(TAG, "Current user has no UserState");
                    // continue starting.
                } else {
                    if (userId == UserHandle.USER_SYSTEM && state.state == STATE_BOOTING) {
                        // system user start explicitly requested. should continue starting as it
                        // is not in running state.
                    } else {
                        if (state.state == STATE_RUNNING_UNLOCKED) {
                            // We'll skip all later code, so we must tell listener it's already
                            // unlocked.
                            notifyFinished(userId, unlockListener);
                        }
                        t.traceEnd(); //getStartedUserState
                        return true;
                    }
                }
            }
            t.traceEnd(); //getStartedUserState

            if (foreground) {
                t.traceBegin("clearAllLockedTasks");
                mInjector.clearAllLockedTasks("startUser");
                t.traceEnd();
            }

            t.traceBegin("getUserInfo");
            final UserInfo userInfo = getUserInfo(userId);
            t.traceEnd();

            if (userInfo == null) {
                Slogf.w(TAG, "No user info for user #" + userId);
                return false;
            }
            if (foreground && userInfo.isProfile()) {
                Slogf.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
                return false;
            }

            if ((foreground || onSecondaryDisplay) && userInfo.preCreated) {
                Slogf.w(TAG, "Cannot start pre-created user #" + userId + " in foreground or on "
                        + "secondary display");
                return false;
            }

            t.traceBegin("assignUserToDisplayOnStart");
            int result = mInjector.getUserManagerInternal().assignUserToDisplayOnStart(userId,
                    userInfo.profileGroupId, userStartMode, displayId);
            t.traceEnd();

            if (result == USER_ASSIGNMENT_RESULT_FAILURE) {
                Slogf.e(TAG, "%s user(%d) / display (%d) assignment failed: %s",
                        userStartModeToString(userStartMode), userId, displayId,
                        userAssignmentResultToString(result));
                return false;
            }

            boolean needStart = false;
            boolean updateUmState = false;
            UserState uss;

            // If the user we are switching to is not currently started, then
            // we need to start it now.
            t.traceBegin("updateStartedUserArrayStarting");
            synchronized (mLock) {
                uss = mStartedUsers.get(userId);
                if (uss == null) {
                    uss = new UserState(UserHandle.of(userId));
                    uss.mUnlockProgress.addListener(new UserProgressListener());
                    mStartedUsers.put(userId, uss);
                    updateStartedUserArrayLU();
                    needStart = true;
                    updateUmState = true;
                } else if (uss.state == UserState.STATE_SHUTDOWN
                        || mDoNotAbortShutdownUserIds.contains(userId)) {
                    Slogf.i(TAG, "User #" + userId
                            + " is shutting down - will start after full shutdown");
                    // TODO(b/416746742): Don't we need to track the displayId too?
                    mPendingUserStarts.add(new PendingUserStart(userId, userStartMode,
                            autoStopUserInSecs, unlockListener));
                    t.traceEnd(); // updateStartedUserArrayStarting
                    return true;
                }
            }

            // No matter what, the fact that we're requested to start the user (even if it is
            // already running) puts it towards the end of the mUserLru list.
            addUserToUserLru(userId);

            if (unlockListener != null) {
                uss.mUnlockProgress.addListener(unlockListener);
            }
            t.traceEnd(); // updateStartedUserArrayStarting

            if (updateUmState) {
                t.traceBegin("setUserState");
                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
                t.traceEnd();
            }

            if (foreground) {
                // Make sure the old user is no longer considering the display to be on.
                mInjector.reportGlobalUsageEvent(UsageEvents.Event.SCREEN_NON_INTERACTIVE);
                synchronized (mLock) {
                    mCurrentUserId = userId;
                    ActivityManager.invalidateGetCurrentUserIdCache();
                }
            }

            UserState finalUss = uss;
            boolean finalNeedStart = needStart;
            final Runnable continueStartUserInternal = () -> continueStartUserInternal(userInfo,
                    oldCurrentUserId, userStartMode, autoStopUserInSecs, finalUss, finalNeedStart,
                    callingUid, callingPid);
            if (foreground) {
                mHandler.post(() -> dispatchOnBeforeUserSwitching(userId, () ->
                        mHandler.post(continueStartUserInternal)));
            } else {
                continueStartUserInternal.run();
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }

        return true;
    }

    private void continueStartUserInternal(UserInfo userInfo, int oldCurUserId,
            int userStartMode, int autoStopUserInSecs, UserState uss, boolean needStart,
            int callingUid, int callingPid) {
        final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
        final boolean foreground = userStartMode == USER_START_MODE_FOREGROUND;
        final int userId = userInfo.id;

        t.traceBegin("updateConfigurationAndProfileIds");
        if (foreground) {
            boolean userSwitchUiEnabled;
            synchronized (mLock) {
                userSwitchUiEnabled = mUserSwitchUiEnabled;
            }
            mInjector.updateUserConfiguration();
            // NOTE: updateProfileRelatedCaches() is called on both if and else parts, ideally
            // it should be moved outside, but for now it's not as there are many calls to
            // external components here afterwards
            updateProfileRelatedCaches();
            if (DesktopExperienceFlags.ENABLE_APPLY_DESK_ACTIVATION_ON_USER_SWITCH.isTrue()) {
                mInjector.getWindowManager().prepareUserStart(userId);
            } else {
                mInjector.getWindowManager().setCurrentUser(userId);
            }
            mInjector.reportCurWakefulnessUsageEvent();
            // Once the internal notion of the active user has switched, we lock the device
            // with the option to show the user switcher on the keyguard.
            if (userSwitchUiEnabled) {
                mInjector.getWindowManager().setSwitchingUser(true);
                // Only lock if the user has a secure keyguard PIN/Pattern/Pwd
                if (mInjector.getKeyguardManager().isDeviceSecure(userId)) {
                    Slogf.d(TAG, "Locking the device before moving on with the user switch");
                    mInjector.lockDeviceNowAndWaitForKeyguardShown();
                }
            }

        } else {
            updateProfileRelatedCaches();
            // We are starting a non-foreground user. They have already been added to the end
            // of mUserLru, so we need to ensure that the foreground user isn't displaced.
            addUserToUserLru(mCurrentUserId);
        }
        t.traceEnd();

        // Make sure user is in the started state.  If it is currently
        // stopping, we need to knock that off.
        if (uss.state == UserState.STATE_STOPPING) {
            t.traceBegin("updateStateStopping");
            // If we are stopping, we haven't sent ACTION_SHUTDOWN,
            // so we can just fairly silently bring the user back from
            // the almost-dead.
            uss.setState(uss.lastState);
            mInjector.getUserManagerInternal().setUserState(userId, uss.state);
            synchronized (mLock) {
                updateStartedUserArrayLU();
            }
            needStart = true;
            t.traceEnd();
        } else if (uss.state == UserState.STATE_SHUTDOWN) {
            t.traceBegin("updateStateShutdown");
            // This means ACTION_SHUTDOWN has been sent, so we will
            // need to treat this as a new boot of the user.
            uss.setState(UserState.STATE_BOOTING);
            mInjector.getUserManagerInternal().setUserState(userId, uss.state);
            synchronized (mLock) {
                updateStartedUserArrayLU();
            }
            needStart = true;
            t.traceEnd();
        }

        if (uss.state == UserState.STATE_BOOTING) {
            t.traceBegin("updateStateBooting");
            // Give user manager a chance to propagate user restrictions
            // to other services and prepare app storage
            mInjector.getUserManager().onBeforeStartUser(userId);

            // Booting up a new user, need to tell system services about it.
            // Note that this is on the same handler as scheduling of broadcasts,
            // which is important because it needs to go first.
            mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId, NO_ARG2));
            t.traceEnd();
        }

        if (android.multiuser.Flags.scheduleStopOfBackgroundUserByDefault()) {
            if (userStartMode != USER_START_MODE_BACKGROUND || isCurrentProfile(userId)) {
                // User isn't (or is no longer) a background user. Clear any prior plans to stop it.
                clearAnyPlansForStoppingBackgroundUser(userId);
            } else {
                // User is a background user. If it's supposed to only run temporarily, schedule it
                // for stopping; otherwise, schedule it for eventual background user judgement.
                if (autoStopUserInSecs > 0 && (needStart || isUserScheduledForStopping(userId))) {
                    // Request is for a temp start and user wasn't already bg-running-in-perpetuity.
                    scheduleStopOfBackgroundUser(userId, autoStopUserInSecs);
                } else {
                    // This wasn't designated a temporary background run. So clear any previous
                    // scheduled stops, and initiate a new inactivity trial.
                    mHandler.removeEqualMessages(SCHEDULE_STOP_BACKGROUND_USER_MSG,
                            Integer.valueOf(userId));
                    initiateJudgeFateOfBackgroundUser(userId);
                }
            }
        } else if (android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
            if (userStartMode == USER_START_MODE_BACKGROUND && !isCurrentProfile(userId) &&
                    autoStopUserInSecs > 0) {
                if (!needStart
                        && !mHandler.hasEqualMessages(SCHEDULE_STOP_BACKGROUND_USER_MSG,
                                Integer.valueOf(userId))) {
                    Slogf.d(TAG, "Not scheduling background user stop: user %d is already running"
                            + " in background in perpetuity, so keep it that way", userId);
                } else {
                    scheduleStopOfBackgroundUser(userId, autoStopUserInSecs);
                }
            } else {
                // This start shouldn't be scheduled for stopping. Clear existing scheduled stops.
                mHandler.removeEqualMessages(SCHEDULE_STOP_BACKGROUND_USER_MSG,
                        Integer.valueOf(userId));
            }
        }

        t.traceBegin("sendMessages");
        if (foreground) {
            mHandler.sendMessage(mHandler.obtainMessage(USER_CURRENT_MSG, userId, oldCurUserId));
            mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
            mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
            mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
                    oldCurUserId, userId, uss));
            mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
                    oldCurUserId, userId, uss), getUserSwitchTimeoutMs());
        }

        if (userInfo.preCreated) {
            needStart = false;
        }

        // In most cases, broadcast for the system user starting/started is sent by
        // ActivityManagerService#systemReady(). However on some HSUM devices (e.g. tablets)
        // the user switches from the system user to a secondary user while running
        // ActivityManagerService#systemReady(), thus broadcast is not sent for the system user.
        // Therefore we send the broadcast for the system user here as well in HSUM.
        // TODO(b/266158156): Improve/refactor the way broadcasts are sent for the system user
        // in HSUM. Ideally it'd be best to have one single place that sends this notification.
        final boolean isSystemUserInHeadlessMode = (userId == UserHandle.USER_SYSTEM)
                && mInjector.isHeadlessSystemUserMode();
        if (needStart || isSystemUserInHeadlessMode) {
            sendUserStartedBroadcast(userId, callingUid, callingPid);
        }
        t.traceEnd();

        if (foreground) {
            t.traceBegin("moveUserToForeground");
            if (DesktopExperienceFlags.ENABLE_APPLY_DESK_ACTIVATION_ON_USER_SWITCH.isTrue()) {
                mInjector.getWindowManager().startUserSwitchTransition(oldCurUserId, userId, uss);
            } else {
                mInjector.getWindowManager().moveUserToForeground(userId, uss,
                        "continueStartUserInternal");
            }
            EventLogTags.writeAmSwitchUser(userId);
            t.traceEnd();
        } else {
            t.traceBegin("finishUserBoot");
            finishUserBoot(uss);
            t.traceEnd();
            if (android.multiuser.Flags.stopExcessForBackgroundStarts()) {
                // We're willing to let this background start exceed maxRunningUsers if it's
                // just an explicitly temporary thing, but otherwise, trim the excess.
                final boolean shouldStopExcessRunningUsers = autoStopUserInSecs <= 0;
                if (shouldStopExcessRunningUsers) {
                    mHandler.post(() -> stopExcessRunningUsers(userId));
                }
            }
        }

        if (needStart || isSystemUserInHeadlessMode) {
            t.traceBegin("sendRestartBroadcast");
            sendUserStartingBroadcast(userId, callingUid, callingPid);
            t.traceEnd();
        }
    }

    /**
     * Start user, if it's not already running, and bring it to foreground.
     */
    void startUserInForeground(@UserIdInt int targetUserId) {
        if (android.multiuser.Flags.setPowerModeDuringUserSwitch()) {
            mInjector.setPerformancePowerMode(true);
        }
        boolean success = startUser(targetUserId, USER_START_MODE_FOREGROUND);
        if (!success) {
            mInjector.getWindowManager().setSwitchingUser(false);
            dismissUserSwitchDialog(this::endUserSwitch);
        }
    }

    boolean unlockUser(@UserIdInt int userId, @Nullable IProgressListener listener) {
        checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "unlockUser");
        EventLog.writeEvent(EventLogTags.UC_UNLOCK_USER, userId);
        final long binderToken = Binder.clearCallingIdentity();
        try {
            return maybeUnlockUser(userId, listener);
        } finally {
            Binder.restoreCallingIdentity(binderToken);
        }
    }

    private static void notifyFinished(@UserIdInt int userId,
            @Nullable IProgressListener listener) {
        if (listener == null) return;
        try {
            listener.onFinished(userId, null);
        } catch (RemoteException ignored) {
        }
    }

    private boolean maybeUnlockUser(@UserIdInt int userId) {
        return maybeUnlockUser(userId, null);
    }

    /**
     * Tries to unlock the given user.
     * <p>
     * This will succeed only if the user's CE storage key is already unlocked or if the user
     * doesn't have a lockscreen credential set.
     */
    private boolean maybeUnlockUser(@UserIdInt int userId, @Nullable IProgressListener listener) {

        // We cannot allow users to be unlocked before PHASE_BOOT_COMPLETED, for two reasons.
        // First, emulated volumes aren't supposed to be used until then; StorageManagerService
        // assumes it can reset everything upon reaching PHASE_BOOT_COMPLETED.  Second, on some
        // devices the Weaver HAL needed to unlock the user's storage isn't available until sometime
        // shortly before PHASE_BOOT_COMPLETED.  The below logic enforces a consistent flow across
        // all devices, regardless of their Weaver implementation.
        //
        // Any unlocks that get delayed by this will be done by onBootComplete() instead.
        if (!mAllowUserUnlocking) {
            Slogf.i(TAG, "Not unlocking user %d yet because boot hasn't completed", userId);
            notifyFinished(userId, listener);
            return false;
        }

        UserState uss;
        if (!StorageManager.isCeStorageUnlocked(userId)) {
            // We always want to try to unlock CE storage, even if the user is not started yet.
            mLockPatternUtils.unlockUserKeyIfUnsecured(userId);
        }
        synchronized (mLock) {
            // Register the given listener to watch for unlock progress
            uss = mStartedUsers.get(userId);
            if (uss != null) {
                uss.mUnlockProgress.addListener(listener);
            }
        }
        // Bail if user isn't actually running
        if (uss == null) {
            notifyFinished(userId, listener);
            return false;
        }

        final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
        t.traceBegin("finishUserUnlocking-" + userId);
        final boolean finishUserUnlockingResult = finishUserUnlocking(uss);
        t.traceEnd();
        if (!finishUserUnlockingResult) {
            notifyFinished(userId, listener);
            return false;
        }

        // We just unlocked a user, so let's now attempt to unlock any profiles under that user.

        // First, get list of userIds. Requires mLock, so we cannot make external calls, e.g. to UMS
        int[] userIds;
        synchronized (mLock) {
            userIds = new int[mStartedUsers.size()];
            for (int i = 0; i < userIds.length; i++) {
                userIds[i] = mStartedUsers.keyAt(i);
            }
        }
        for (int testUserId : userIds) {
            final UserInfo parent = mInjector.getUserManager().getProfileParent(testUserId);
            if (parent != null && parent.id == userId && testUserId != userId) {
                Slogf.d(TAG, "User " + testUserId + " (parent " + parent.id
                        + "): attempting unlock because parent was just unlocked");
                maybeUnlockUser(testUserId);
            }
        }

        return true;
    }

    boolean switchUser(final int targetUserId) {
        enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, targetUserId);
        EventLog.writeEvent(EventLogTags.UC_SWITCH_USER, targetUserId);
        int currentUserId = getCurrentUserId();
        UserInfo targetUserInfo = getUserInfo(targetUserId);
        boolean userSwitchUiEnabled;
        synchronized (mLock) {
            if (targetUserId == currentUserId && mTargetUserId == UserHandle.USER_NULL) {
                Slogf.i(TAG, "user #" + targetUserId + " is already the current user");
                mHandler.post(this::endUserSwitch);
                return true;
            }
            if (targetUserInfo == null) {
                Slogf.w(TAG, "No user info for user #" + targetUserId);
                return false;
            }
            if (!mInjector.doesUserSupportSwitchTo(targetUserInfo)) {
                Slogf.w(TAG, "Cannot switch to User #" + targetUserId + ": not supported");
                return false;
            }
            if (FactoryResetter.isFactoryResetting()) {
                Slogf.w(TAG, "Cannot switch to User #" + targetUserId
                        + ": factory reset in progress");
                return false;
            }

            if (!mInitialized) {
                Slogf.e(TAG, "Cannot switch to User #" + targetUserId
                        + ": UserController not ready yet");
                return false;
            }
            if (mTargetUserId != UserHandle.USER_NULL) {
                Slogf.w(TAG, "There is already an ongoing user switch to User #" + mTargetUserId
                        + ". User #" + targetUserId + " will be added to the queue.");
                mPendingTargetUserIds.offer(targetUserId);
                return true;
            }
            mTargetUserId = targetUserId;
            ActivityManager.invalidateGetCurrentUserIdCache();
            userSwitchUiEnabled = mUserSwitchUiEnabled;
        }
        if (userSwitchUiEnabled) {
            UserInfo currentUserInfo = getUserInfo(currentUserId);
            Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
            mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
            mUiHandler.sendMessage(mUiHandler.obtainMessage(
                    START_USER_SWITCH_UI_MSG, userNames));
        } else {
            sendStartUserSwitchFgMessage(targetUserId);
        }
        return true;
    }

    private void sendStartUserSwitchFgMessage(int targetUserId) {
        mHandler.removeMessages(START_USER_SWITCH_FG_MSG);
        mHandler.sendMessage(mHandler.obtainMessage(START_USER_SWITCH_FG_MSG, targetUserId, 0));
    }

    private void dismissUserSwitchDialog(Runnable onDismissed) {
        mUiHandler.post(() -> mInjector.dismissUserSwitchingDialog(onDismissed));
    }

    private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) {
        // The dialog will show and then initiate the user switch by calling startUserInForeground
        mInjector.showUserSwitchingDialog(fromToUserPair.first, fromToUserPair.second,
                getSwitchingFromUserMessageUnchecked(fromToUserPair.first.id),
                getSwitchingToUserMessageUnchecked(fromToUserPair.second.id),
                /* onShown= */ () -> sendStartUserSwitchFgMessage(fromToUserPair.second.id));
    }

    private void dispatchForegroundProfileChanged(@UserIdInt int userId) {
        final int observerCount = mUserSwitchObservers.beginBroadcast();
        for (int i = 0; i < observerCount; i++) {
            try {
                mUserSwitchObservers.getBroadcastItem(i).onForegroundProfileSwitch(userId);
            } catch (RemoteException e) {
                // Ignore
            }
        }
        mUserSwitchObservers.finishBroadcast();
    }

    private void dispatchOnBeforeUserSwitching(@UserIdInt int newUserId, Runnable onComplete) {
        final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
        t.traceBegin("dispatchOnBeforeUserSwitching-" + newUserId);
        final AtomicBoolean isFirst = new AtomicBoolean(true);
        startTimeoutForOnBeforeUserSwitching(isFirst, onComplete);
        informUserSwitchObservers((observer, callback) -> {
            try {
                observer.onBeforeUserSwitching(newUserId, callback);
            } catch (RemoteException e) {
                // ignore
            }
        }, () -> {
            if (isFirst.getAndSet(false)) {
                onComplete.run();
            }
        }, "onBeforeUserSwitching");
        t.traceEnd();
    }

    private void startTimeoutForOnBeforeUserSwitching(AtomicBoolean isFirst,
            Runnable onComplete) {
        final long timeout = getUserSwitchTimeoutMs();
        mHandler.postDelayed(() -> {
            if (isFirst.getAndSet(false)) {
                String unresponsiveObservers;
                synchronized (mLock) {
                    unresponsiveObservers = String.join(", ", mCurWaitingUserSwitchCallbacks);
                }
                Slogf.e(TAG, "Timeout on dispatchOnBeforeUserSwitching. These UserSwitchObservers "
                        + "did not respond in " + timeout + "ms: " + unresponsiveObservers + ".");
                onComplete.run();
            }
        }, timeout);
    }


    /** Called on handler thread */
    @VisibleForTesting
    void dispatchUserSwitchComplete(@UserIdInt int oldUserId, @UserIdInt int newUserId) {
        final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
        t.traceBegin("dispatchUserSwitchComplete-" + newUserId);
        mInjector.getWindowManager().setSwitchingUser(false);
        final int observerCount = mUserSwitchObservers.beginBroadcast();
        for (int i = 0; i < observerCount; i++) {
            try {
                t.traceBegin("onUserSwitchComplete-" + newUserId + " #" + i + " "
                        + mUserSwitchObservers.getBroadcastCookie(i));
                mUserSwitchObservers.getBroadcastItem(i).onUserSwitchComplete(newUserId);
                t.traceEnd();
            } catch (RemoteException e) {
                // Ignore
            }
        }
        mUserSwitchObservers.finishBroadcast();
        t.traceBegin("sendUserSwitchBroadcasts-" + oldUserId + "-" + newUserId);
        sendUserSwitchBroadcasts(oldUserId, newUserId);
        t.traceEnd();
        t.traceEnd();

        endUserSwitch();
    }

    private void endUserSwitch() {
        if (android.multiuser.Flags.setPowerModeDuringUserSwitch()) {
            mInjector.setPerformancePowerMode(false);
        }
        final int nextUserId;
        final int stopUserId;
        synchronized (mLock) {
            nextUserId = ObjectUtils.getOrElse(mPendingTargetUserIds.poll(),
                    mPendingLogoutTargetUser ? UserHandle.USER_SYSTEM : UserHandle.USER_NULL);
            stopUserId = mPendingLogoutTargetUser ? mTargetUserId : UserHandle.USER_NULL;
            mPendingLogoutTargetUser = false;
            mTargetUserId = UserHandle.USER_NULL;
            ActivityManager.invalidateGetCurrentUserIdCache();
        }
        if (nextUserId != UserHandle.USER_NULL) {
            switchUser(nextUserId);
        }
        if (stopUserId != UserHandle.USER_NULL) {
            stopUser(stopUserId, /* allowDelayedLocking= */ false, /* stopUserCallback= */ null,
                    /* keyEvictedCallback= */ null);
        }
    }

    private void dispatchLockedBootComplete(@UserIdInt int userId) {
        final int observerCount = mUserSwitchObservers.beginBroadcast();
        for (int i = 0; i < observerCount; i++) {
            try {
                mUserSwitchObservers.getBroadcastItem(i).onLockedBootComplete(userId);
            } catch (RemoteException e) {
                // Ignore
            }
        }
        mUserSwitchObservers.finishBroadcast();
    }

    /**
     * Possibly stops the given full user (or its profile) when we switch out of it, if dictated
     * by policy.
     */
    private void stopUserOnSwitchIfEnforced(@UserIdInt int oldUserId) {
        // Never stop system user
        if (oldUserId == UserHandle.USER_SYSTEM) {
            return;
        }
        boolean hasRestriction =
                hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, oldUserId);
        synchronized (mLock) {
            // If running in background is disabled or mStopUserOnSwitch mode, stop the user.
            if (hasRestriction || isStopUserOnSwitchEnabled()) {
                Slogf.i(TAG, "Stopping user %d and its profiles on user switch", oldUserId);
                stopUsersLU(oldUserId, /* allowDelayedLocking= */ !hasRestriction, null, null);
                return;
            }
        }

        // We didn't need to stop the parent, but perhaps one of its profiles needs to be stopped.
        final List<UserInfo> profiles = mInjector.getUserManager().getProfiles(
                oldUserId, /* enabledOnly= */ false);
        final int count = profiles.size();
        for (int i = 0; i < count; i++) {
            final int profileUserId = profiles.get(i).id;
            if (hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, profileUserId)) {
                Slogf.i(TAG, "Stopping profile %d on user switch", profileUserId);
                synchronized (mLock) {
                    stopUsersLU(profileUserId,
                            /* stopProfileRegardlessOfParent= */ false,
                            /* allowDelayedLocking= */ false, null, null);
                }
            }
        }
    }

    /**
     * Returns whether the given user is a background user that is possible to be stopped via
     * scheduled stopping, if the need arises.
     */
    private boolean isBackgroundUserEligibleForAutomaticStopping(@UserIdInt int userId) {
        if (!android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
            return false;
        }
        if (UserManager.isVisibleBackgroundUsersEnabled()) {
            // Feature is not enabled on this device. Consider enabling it after testing it.
            return false;
        }
        if (userId == UserHandle.USER_SYSTEM) {
            // Never stop system user.
            return false;
        }
        if (isUserVisible(userId)) {
            // User is visible, possibly on a background display or as an alwaysVisibleUser.
            return false;
        }
        synchronized (mLock) {
            if (isCurrentProfile(userId)) {
                // The user, or its parent, is the current user.
                return false;
            }
            final UserState uss = mStartedUsers.get(userId);
            if (uss == null || uss.state == UserState.STATE_STOPPING
                    || uss.state == UserState.STATE_SHUTDOWN) {
                // We've stopped (or are stopping) the user anyway, so no point scheduling.
                return false;
            }
        }
        return true;
    }

    /**
     * Possibly schedules the user to be judged at a future point. Such judgement will
     * decide whether the background user hasn't been actively used in a long time and
     * whether to therefore automatically stop it.
     *
     * This is only intended for users that are currently in the background.
     * A newly scheduled trial will replace any previously scheduled trial.
     */
    private void initiateJudgeFateOfBackgroundUser(@UserIdInt int userId) {
        if (mBackgroundUserConsideredDispensableTimeSecs < 0) {
            // Feature is not enabled on this device.
            return;
        }
        if (!isBackgroundUserEligibleForAutomaticStopping(userId)) {
            // User is not eligible to be automatically stopped.
            Slogf.i(TAG, "Exempting user %d from judgement since it is ineligible", userId);
            return;
        }
        if (isUserScheduledForStopping(userId)) {
            // The user's fate is already sealed; we're planning on stopping it anyway.
            Slogf.i(TAG, "Skipping judgement of user %d since it scheduled to stop anyway", userId);
            return;
        }
        if (userId == mInjector.getUserManagerInternal().getMainUserId()) {
            // MainUser is currently special for things like Docking, so we'll exempt it for now.
            Slogf.i(TAG, "Exempting user %d from judgement since it is the main user", userId);
            return;
        }

        // First clear any existing trial for this user.
        ceaseJudgeFateOfBackgroundUser(userId);

        Slogf.i(TAG, "Scheduling to judge background user %d in %d seconds", userId,
                POLLING_TIME_FOR_JUDGING_BACKGROUND_USER_SECS);
        // It is too soon to record the lmkdKillCount. It takes a few minutes for the new background
        // user's processes' priorities to drop, so lmks wouldn't reflect on ths user. Moreover,
        // the user event itself might trigger lmks which we don't want to consider. So wait a bit.
        // Give a lmk threshold of -1 to signify that we have yet to determine it.
        final UserAndLmkThreshold userWithoutLmk = new UserAndLmkThreshold(userId, -1);
        mHandler.sendMessageDelayed(
                mHandler.obtainMessage(SCHEDULE_JUDGE_FATE_OF_BACKGROUND_USER_MSG, userWithoutLmk),
                POLLING_TIME_FOR_JUDGING_BACKGROUND_USER_SECS * 1000);
    }

    /**
     * Judge the fate of the background user. If the user has been in the background for a long
     * time, and there has been memory pressure, and it is likely that most of its regular apps have
     * been killed anyway, then we can sentence it for stopping.
     *
     * In implementation, there are multiple phases in this judgement trial:<ul>
     * <li>Phase 1: Wait a few minutes for its oom scores to deprioritize after entering the bg.
     * <li>Phase 2: Wait a long time to conclude that the user is indeed dispensable.
     * <li>Phase 3: Keep polling the lmkCount periodically to see if there has been
     * memory pressure (i.e. whether the lmkCount has increased above the value at the end of
     * Phase 1), and if so, render our verdict.
     * <li>Phase 4: Sentence the user to stopping.
     * </ul>
     *
     * @param userAndLmkThreshold the background user under consideration, coupled with the
     *                            lmkCountThreshold above which we would decide to stop this user if
     *                            it had been inactive for sufficiently long. A lmkCountThreshold
     *                            value below 0 indicates that we haven't yet recorded it.
     */
    @VisibleForTesting
    void processJudgeFateOfBackgroundUser(UserAndLmkThreshold userAndLmkThreshold) {
        if (mBackgroundUserConsideredDispensableTimeSecs < 0) {
            // Feature is not enabled on this device. This line should be unreachable.
            return;
        }
        final @UserIdInt int userId = userAndLmkThreshold.userId;
        if (!isBackgroundUserEligibleForAutomaticStopping(userId)
                || isUserScheduledForStopping(userId)) {
            // Sanity check. Make sure the user is still indeed running non-visibly in the
            // background and isn't already slated for stopping.
            return;
        }

        // Phase 1 was already initiated by initiateJudgeFateOfBackgroundUser() and has finished.
        final int lmkCountThreshold = userAndLmkThreshold.lmkCountThreshold;
        final int currentLmkCount = mInjector.getLmkdKillCount();
        Slogf.v(TAG, "Comparing current lmkCount %d with threshold level %d for judgement",
                currentLmkCount, lmkCountThreshold);

        if (lmkCountThreshold < 0) {
            // Entering Phase 2. Time to gather evidence.
            // The user only recently entered the background; now we wait to see if the user stays
            // there for a long time. We also record the initial lmkCount value.
            // (Can also happen if lmkdKillCount has yet to give us an initial valid value; we'll
            // try again later, but don't want to poll too frequently if it isn't working anyway.)
            Slogf.i(TAG, "Scheduling judgement of background user %d in %d secs with %d lmks",
                    userId, mBackgroundUserConsideredDispensableTimeSecs, currentLmkCount);
            mHandler.sendMessageDelayed(mHandler.obtainMessage(
                    SCHEDULE_JUDGE_FATE_OF_BACKGROUND_USER_MSG,
                            new UserAndLmkThreshold(userId, currentLmkCount)),
                    mBackgroundUserConsideredDispensableTimeSecs * 1000);
        } else if (currentLmkCount <= lmkCountThreshold) {
            // We are in Phase 3.
            // currentLmkCount hasn't increased (or is invalid). Check again soon.
            Slogf.i(TAG, "Judgement: User %d is dispensable; wait %d secs to see if lmks exceed %d",
                    userId, POLLING_TIME_FOR_JUDGING_BACKGROUND_USER_SECS, lmkCountThreshold);
            mHandler.sendMessageDelayed(mHandler.obtainMessage(
                            SCHEDULE_JUDGE_FATE_OF_BACKGROUND_USER_MSG, userAndLmkThreshold),
                    POLLING_TIME_FOR_JUDGING_BACKGROUND_USER_SECS * 1000);
        } else { // 0 <= lmkCountThreshold < currentLmkCount
            // Entering Phase 4.
            // The user has been in the background for the requisite length of time and lmkCount has
            // increased. We render the verdict to stop the user.
            // Rather than stop it immediately, we'll delay the sentence in case the recent lmks
            // indicate recent CPU stress too, and also to benefit from the scheduling checks.
            Slogf.i(TAG, "Judgement: User %d is sentenced for stopping", userId);
            rescheduleStopOfBackgroundUser(userId);
        }
    }

    /**
     * Cancels both SCHEDULE_STOP_BACKGROUND_USER_MSG and SCHEDULE_JUDGE_FATE_OF_BACKGROUND_USER_MSG
     * for the given user.
     */
    private void clearAnyPlansForStoppingBackgroundUser(@UserIdInt Integer userIdInteger) {
        if (android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
            mHandler.removeEqualMessages(SCHEDULE_STOP_BACKGROUND_USER_MSG, userIdInteger);
        }
        ceaseJudgeFateOfBackgroundUser(userIdInteger);
    }

    private void ceaseJudgeFateOfBackgroundUser(@UserIdInt Integer userIdInteger) {
        if (!android.multiuser.Flags.scheduleStopOfBackgroundUserByDefault()) {
            return;
        }
        final UserAndLmkThreshold userAndAnyLmk = new UserAndLmkThreshold(userIdInteger, 0);
        mHandler.removeEqualMessages(SCHEDULE_JUDGE_FATE_OF_BACKGROUND_USER_MSG, userAndAnyLmk);
    }

    private boolean isUserScheduledForStopping(@UserIdInt Integer userIdInteger) {
        return mHandler.hasEqualMessages(SCHEDULE_STOP_BACKGROUND_USER_MSG, userIdInteger);
    }

    /**
     * Possibly schedules the user to be stopped at a future point, even though we had originally
     * wanted to stop it now. For example, perhaps we have to postpone a scheduled user stop for
     * some reason, and therefore are rescheduling it until a later point.
     *
     * This is only intended for background users (including profiles).
     */
    private boolean rescheduleStopOfBackgroundUser(@UserIdInt Integer userIdInteger) {
        // Clear any previous schedule (since we've already evaluated that we need to postpone) if
        // necessary, and schedule stopping the user soon.
        mHandler.removeEqualMessages(SCHEDULE_STOP_BACKGROUND_USER_MSG, userIdInteger);
        return scheduleStopOfBackgroundUser(userIdInteger,
                POSTPONEMENT_TIME_FOR_BACKGROUND_USER_STOP_SECS);
    }

    /**
     * Possibly schedules the user to be stopped after the given number of seconds.
     *
     * This is only intended for background users (including profiles).
     *
     * Supplements, rather than replaces, any previous schedule. Ultimately, the future-most
     * scheduled stop is what matters.
     */
    private boolean scheduleStopOfBackgroundUser(@UserIdInt int userId, int delayUptimeSecs) {
        if (!android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
            return false;
        }
        if (delayUptimeSecs <= 0) {
            return false;
        }
        if (!isBackgroundUserEligibleForAutomaticStopping(userId)) {
            Slogf.i(TAG, "Not scheduling to stop user %d since it's ineligible", userId);
            return false;
        }

        // We're deciding to stop the user, so its fate is already sealed. No point judging it now.
        ceaseJudgeFateOfBackgroundUser(userId);

        Slogf.i(TAG, "Scheduling to stop user %d in %d seconds", userId, delayUptimeSecs);
        final int delayUptimeMs = delayUptimeSecs * 1000;
        final Object msgObj = userId;
        mHandler.sendMessageDelayed(
                mHandler.obtainMessage(SCHEDULE_STOP_BACKGROUND_USER_MSG, msgObj),
                delayUptimeMs);
        return true;
    }

    /**
     * Possibly stops the given user due to it having been in the background for a long time.
     * There is no guarantee of stopping the user; it is done discretionarily.
     *
     * This should never be called for background visible users; devices that support this should
     * not use {@link #scheduleStopOfBackgroundUser(int, int)}.
     *
     * @param userIdInteger a user to be stopped if it is still in the background
     */
    @VisibleForTesting
    void processScheduledStopOfBackgroundUser(Integer userIdInteger) {
        final int userId = userIdInteger;
        Slogf.d(TAG, "Considering stopping-on-schedule background user %d", userId);

        if (isUserScheduledForStopping(userIdInteger)) {
            Slogf.i(TAG, "User %d is scheduled for bg stopping later, so wait until then", userId);
            return;
        }
        if (avoidStoppingUserRightNow(userId)) {
            Slogf.i(TAG, "Rescheduling bg stopping of user %d because it (or its profile) should "
                    + "not be stopped right now ", userId);
            rescheduleStopOfBackgroundUser(userIdInteger);
            return;
        }

        synchronized (mLock) {
            final Integer userOrParent = mUserProfileGroupIds.get(userId, userIdInteger);
            if (getCurrentOrTargetUserIdLU() == userOrParent) {
                // User is (somehow) already in the foreground, or we're currently switching to it.
                return;
            }
            if (mPendingTargetUserIds.contains(userOrParent)) {
                // We'll soon want to switch to this user, so don't kill it now.
                return;
            }
            final UserInfo currentOrTargetUser = getCurrentUserLU();
            if (currentOrTargetUser != null && currentOrTargetUser.isGuest()) {
                // Don't kill any background users for the sake of a Guest. Just reschedule instead.
                Slogf.i(TAG, "Current user %d is a Guest, so reschedule bg stopping of user %d",
                        currentOrTargetUser.id, userId);
                rescheduleStopOfBackgroundUser(userIdInteger);
                return;
            }
            Slogf.i(TAG, "Stopping-on-schedule background user %d", userId);
            stopUsersLU(userId, /* stopProfileRegardlessOfParent= */ false,
                    /* allowDelayedLocking= */ true, null, null);
        }
    }

    /**
     * Returns whether to avoid stopping the given user because, even though it may only be in the
     * background, it (or {@link #getUsersToStopLU(int) a user that would stop if it stopped}) is
     * still currently useful (e.g. playing audio, or has an upcoming alarm).
     *
     * Makes requests of other services, so don't call while holding a lock.
     */
    private boolean avoidStoppingUserRightNow(@UserIdInt int userId) {
        return avoidStoppingUserRightNow(userId, mInjector.getVisibleActivityUsers());
    }

    /**
     * Same as {@link #avoidStoppingUserRightNow(int)} but, for efficiency, takes the output of
     * {@link Injector#getVisibleActivityUsers()} as a parameter.
     */
    private boolean avoidStoppingUserRightNow(
            @UserIdInt int userId, ArraySet<Integer> visibleActivityUsers) {

        if (!android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
            return false;
        }
        final int[] usersThatWouldStop;
        synchronized (mLock) {
            usersThatWouldStop = getUsersToStopLU(userId);
        }
        for (int relatedUserId : usersThatWouldStop) {
            if (avoidStoppingUserDueToUpcomingAlarm(relatedUserId)) {
                // We want this user running soon for alarm-purposes, so don't stop it now.
                Slogf.d(TAG, "User %d shouldn't be stopped because user %d will fire an alarm soon",
                        userId, relatedUserId);
                return true;
            }
            if (mInjector.getAudioManagerInternal().isUserPlayingAudio(relatedUserId)) {
                // User is audible (even if invisibly, e.g. via an alarm), so don't stop it.
                Slogf.d(TAG, "User %d shouldn't be stopped because user %d is playing audio",
                        userId, relatedUserId);
                return true;
            }
            if (visibleActivityUsers.contains(userId)) {
                // User is displaying the top activity from a currently visible root task.
                Slogf.d(TAG, "User %d shouldn't be stopped because user %d has a visible activity",
                        userId, relatedUserId);
                return true;
            }
        }
        return false;
    }

    /**
     * Returns whether we should avoid stopping the user now due to it having an alarm set to fire
     * soon.
     */
    private boolean avoidStoppingUserDueToUpcomingAlarm(@UserIdInt int userId) {
        final long alarmWallclockMs
                = mInjector.getAlarmManagerInternal().getNextAlarmTriggerTimeForUser(userId);
        final long now = System.currentTimeMillis();
        return now < alarmWallclockMs
                && (alarmWallclockMs < now + TIME_BEFORE_USERS_ALARM_TO_AVOID_STOPPING_MS);
    }

    private void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
        TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
        t.traceBegin("timeoutUserSwitch-" + oldUserId + "-to-" + newUserId);
        synchronized (mLock) {
            Slogf.e(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
            mTimeoutUserSwitchCallbacks = mCurWaitingUserSwitchCallbacks;
            mHandler.removeMessages(USER_SWITCH_CALLBACKS_TIMEOUT_MSG);
            sendContinueUserSwitchLU(uss, oldUserId, newUserId);
            // Report observers that never called back (USER_SWITCH_CALLBACKS_TIMEOUT)
            mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_CALLBACKS_TIMEOUT_MSG,
                    oldUserId, newUserId), USER_SWITCH_CALLBACKS_TIMEOUT_MS);
        }
        t.traceEnd();
    }

    private void timeoutUserSwitchCallbacks(int oldUserId, int newUserId) {
        synchronized (mLock) {
            if (mTimeoutUserSwitchCallbacks != null && !mTimeoutUserSwitchCallbacks.isEmpty()) {
                Slogf.wtf(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId
                        + ". Observers that didn't respond: " + mTimeoutUserSwitchCallbacks);
                mTimeoutUserSwitchCallbacks = null;
            }
        }
    }

    @VisibleForTesting
    void dispatchUserSwitch(final UserState uss, final int oldUserId, final int newUserId) {
        final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
        t.traceBegin("dispatchUserSwitch-" + oldUserId + "-to-" + newUserId);

        EventLog.writeEvent(EventLogTags.UC_DISPATCH_USER_SWITCH, oldUserId, newUserId);
        uss.switching = true;
        informUserSwitchObservers((observer, callback) -> {
            try {
                observer.onUserSwitching(newUserId, callback);
            } catch (RemoteException e) {
                // ignore
            }
        }, () -> {
            synchronized (mLock) {
                sendContinueUserSwitchLU(uss, oldUserId, newUserId);
            }
        }, "onUserSwitching");
        t.traceEnd();
    }

    void informUserSwitchObservers(BiConsumer<IUserSwitchObserver, IRemoteCallback> consumer,
            final Runnable onComplete, String trace) {
        final int observerCount = mUserSwitchObservers.beginBroadcast();
        if (observerCount == 0) {
            onComplete.run();
            mUserSwitchObservers.finishBroadcast();
            return;
        }
        final ArraySet<String> curWaitingUserSwitchCallbacks = new ArraySet<>();
        synchronized (mLock) {
            mCurWaitingUserSwitchCallbacks = curWaitingUserSwitchCallbacks;
        }
        final AtomicInteger waitingCallbacksCount = new AtomicInteger(observerCount);
        final long userSwitchTimeoutMs = getUserSwitchTimeoutMs();
        final long dispatchStartedTime = SystemClock.elapsedRealtime();
        for (int i = 0; i < observerCount; i++) {
            final long dispatchStartedTimeForObserver = SystemClock.elapsedRealtime();
            // Prepend with unique prefix to guarantee that keys are unique
            final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i);
            synchronized (mLock) {
                curWaitingUserSwitchCallbacks.add(name);
            }
            final IRemoteCallback callback = new IRemoteCallback.Stub() {
                @Override
                public void sendResult(Bundle data) throws RemoteException {
                    asyncTraceEnd(trace + "-" + name, 0);
                    synchronized (mLock) {
                        long delayForObserver = SystemClock.elapsedRealtime()
                                - dispatchStartedTimeForObserver;
                        if (delayForObserver > LONG_USER_SWITCH_OBSERVER_WARNING_TIME_MS) {
                            Slogf.w(TAG, "User switch slowed down by observer " + name
                                    + ": result took " + delayForObserver
                                    + " ms to process. " + trace);
                        }
                        long totalDelay = SystemClock.elapsedRealtime() - dispatchStartedTime;
                        if (totalDelay > userSwitchTimeoutMs) {
                            Slogf.e(TAG, "User switch timeout: observer " + name
                                    + "'s result was received " + totalDelay
                                    + " ms after dispatchUserSwitch. " + trace);
                        }
                        curWaitingUserSwitchCallbacks.remove(name);
                        // Continue switching if all callbacks have been notified and
                        // user switching session is still valid
                        if (waitingCallbacksCount.decrementAndGet() == 0
                                && (curWaitingUserSwitchCallbacks
                                == mCurWaitingUserSwitchCallbacks)) {
                            onComplete.run();
                        }
                    }
                }
            };
            asyncTraceBegin(trace + "-" + name, 0);
            consumer.accept(mUserSwitchObservers.getBroadcastItem(i), callback);
        }
        mUserSwitchObservers.finishBroadcast();
    }

    @GuardedBy("mLock")
    private void sendContinueUserSwitchLU(UserState uss, int oldUserId, int newUserId) {
        TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
        t.traceBegin("sendContinueUserSwitchLU-" + oldUserId + "-to-" + newUserId);
        mCurWaitingUserSwitchCallbacks = null;
        mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
        mHandler.sendMessage(mHandler.obtainMessage(CONTINUE_USER_SWITCH_MSG,
                oldUserId, newUserId, uss));
        t.traceEnd();
    }

    @VisibleForTesting
    void continueUserSwitch(UserState uss, int oldUserId, int newUserId) {
        final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
        t.traceBegin("continueUserSwitch-" + oldUserId + "-to-" + newUserId);

        EventLog.writeEvent(EventLogTags.UC_CONTINUE_USER_SWITCH, oldUserId, newUserId);

        // Dismiss the user switching dialog and complete the user switch
        mHandler.removeMessages(COMPLETE_USER_SWITCH_MSG);
        mHandler.sendMessage(mHandler.obtainMessage(
                COMPLETE_USER_SWITCH_MSG, oldUserId, newUserId));

        uss.switching = false;
        stopGuestOrEphemeralUserIfBackground(oldUserId);
        stopUserOnSwitchIfEnforced(oldUserId);
        initiateJudgeFateOfBackgroundUser(oldUserId);

        t.traceEnd(); // end continueUserSwitch
    }

    @VisibleForTesting
    void completeUserSwitch(int oldUserId, int newUserId) {
        final Runnable runnable = () -> {
            // Send REPORT_USER_SWITCH_COMPLETE_MSG to broadcast ACTION_USER_SWITCHED and call
            // onUserSwitchComplete on UserSwitchObservers.
            mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
            mHandler.sendMessage(mHandler.obtainMessage(
                    REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId));
        };
        if (isUserSwitchUiEnabled()) {
            dismissUserSwitchDialog(runnable);
        } else {
            runnable.run();
        }
    }

    // The two methods sendUserStartedBroadcast() and sendUserStartingBroadcast()
    // could be merged for better reuse. However, the params they are calling broadcastIntent()
    // with are different - resultCode receiver, permissions, ordered, and userId, etc. Therefore,
    // we decided to keep two separate methods for better code readability/clarity.
    // TODO(b/266158156): Improve/refactor the way broadcasts are sent for the system user
    // in HSUM. Ideally it'd be best to have one single place that sends this notification.
    /** Sends {@code ACTION_USER_STARTED} broadcast. */
    void sendUserStartedBroadcast(@UserIdInt int userId, int callingUid, int callingPid) {
        if (userId == UserHandle.USER_SYSTEM) {
            synchronized (mLock) {
                // Make sure that the broadcast is sent only once for the system user.
                if (mIsBroadcastSentForSystemUserStarted) {
                    return;
                }
                mIsBroadcastSentForSystemUserStarted = true;
            }
        }
        final Intent intent = new Intent(Intent.ACTION_USER_STARTED);
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                | Intent.FLAG_RECEIVER_FOREGROUND);
        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
        mInjector.broadcastIntent(intent, /* resolvedType= */ null, /* resultTo= */ null,
                /* resultCode= */ 0, /* resultData= */ null, /* resultExtras= */ null,
                /* requiredPermissions= */ null, AppOpsManager.OP_NONE, /* bOptions= */ null,
                /* sticky= */ false, MY_PID, SYSTEM_UID,
                callingUid, callingPid, userId);
    }

    /** Sends {@code ACTION_USER_STARTING} broadcast. */
    void sendUserStartingBroadcast(@UserIdInt int userId, int callingUid, int callingPid) {
        if (userId == UserHandle.USER_SYSTEM) {
            synchronized (mLock) {
                // Make sure that the broadcast is sent only once for the system user.
                if (mIsBroadcastSentForSystemUserStarting) {
                    return;
                }
                mIsBroadcastSentForSystemUserStarting = true;
            }
        }
        final Intent intent = new Intent(Intent.ACTION_USER_STARTING);
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
        mInjector.broadcastIntent(intent, /* resolvedType= */ null,
                new IIntentReceiver.Stub() {
                    @Override
                    public void performReceive(Intent intent, int resultCode,
                            String data, Bundle extras, boolean ordered,
                            boolean sticky,
                            int sendingUser) throws RemoteException {
                    }
                }, /* resultCode= */ 0, /* resultData= */ null, /* resultExtras= */ null,
                new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE, /* bOptions= */ null,
                /* sticky= */ false, MY_PID, SYSTEM_UID,
                callingUid, callingPid, UserHandle.USER_ALL);
    }

    void sendUserSwitchBroadcasts(int oldUserId, int newUserId) {
        final int callingUid = Binder.getCallingUid();
        final int callingPid = Binder.getCallingPid();
        final long ident = Binder.clearCallingIdentity();
        try {
            Intent intent;
            if (oldUserId >= 0) {
                // Send USER_BACKGROUND broadcast to all profiles of the outgoing user
                List<UserInfo> profiles = mInjector.getUserManager().getProfiles(oldUserId, false);
                int count = profiles.size();
                for (int i = 0; i < count; i++) {
                    int profileUserId = profiles.get(i).id;
                    intent = new Intent(Intent.ACTION_USER_BACKGROUND);
                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                            | Intent.FLAG_RECEIVER_FOREGROUND);
                    intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
                    // Also, add the UserHandle for mainline modules which can't use the @hide
                    // EXTRA_USER_HANDLE.
                    intent.putExtra(Intent.EXTRA_USER, UserHandle.of(profileUserId));
                    mInjector.broadcastIntent(intent,
                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                            null, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
                            profileUserId);
                }
            }
            if (newUserId >= 0) {
                // Send USER_FOREGROUND broadcast to all profiles of the incoming user
                List<UserInfo> profiles = mInjector.getUserManager().getProfiles(newUserId, false);
                int count = profiles.size();
                for (int i = 0; i < count; i++) {
                    int profileUserId = profiles.get(i).id;
                    intent = new Intent(Intent.ACTION_USER_FOREGROUND);
                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                            | Intent.FLAG_RECEIVER_FOREGROUND);
                    intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
                    // Also, add the UserHandle for mainline modules which can't use the @hide
                    // EXTRA_USER_HANDLE.
                    intent.putExtra(Intent.EXTRA_USER, UserHandle.of(profileUserId));
                    mInjector.broadcastIntent(intent,
                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                            null, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
                            profileUserId);
                }
                intent = new Intent(Intent.ACTION_USER_SWITCHED);
                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                        | Intent.FLAG_RECEIVER_FOREGROUND);
                intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
                // Also, add the UserHandle for mainline modules which can't use the @hide
                // EXTRA_USER_HANDLE.
                intent.putExtra(Intent.EXTRA_USER, UserHandle.of(newUserId));
                mInjector.broadcastIntent(intent,
                        null, null, 0, null, null,
                        new String[] {android.Manifest.permission.MANAGE_USERS},
                        AppOpsManager.OP_NONE, null, false, MY_PID, SYSTEM_UID, callingUid,
                        callingPid, UserHandle.USER_ALL);
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

    /**
     * Broadcasts to the parent user when a profile is started+unlocked/stopped.
     * @param userId the id of the profile
     * @param parentId the id of the parent user
     * @param intentAction either ACTION_PROFILE_ACCESSIBLE or ACTION_PROFILE_INACCESSIBLE
     */
    private void broadcastProfileAccessibleStateChanged(@UserIdInt int userId,
            @UserIdInt int parentId,
            String intentAction) {
        final Intent intent = new Intent(intentAction);
        intent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                | Intent.FLAG_RECEIVER_FOREGROUND);
        mInjector.broadcastIntent(intent, /* resolvedType= */ null, /* resultTo= */
                null, /* resultCode= */ 0, /* resultData= */ null, /* resultExtras= */
                null, /* requiredPermissions= */ null, AppOpsManager.OP_NONE, /* bOptions= */
                null, /* sticky= */ false, MY_PID, SYSTEM_UID,
                Binder.getCallingUid(), Binder.getCallingPid(), parentId);
    }

    @CanBeALL @UserIdInt int handleIncomingUser(int callingPid, int callingUid,
            @CanBeALL @CanBeCURRENT @CanBeCURRENT_OR_SELF @UserIdInt int userId, boolean allowAll,
            int allowMode, String name, String callerPackage) {
        final int callingUserId = UserHandle.getUserId(callingUid);
        if (callingUserId == userId) {
            return userId;
        }

        // Note that we may be accessing mCurrentUserId outside of a lock...
        // shouldn't be a big deal, if this is being called outside
        // of a locked context there is intrinsically a race with
        // the value the caller will receive and someone else changing it.
        // We assume that USER_CURRENT_OR_SELF will use the current user; later
        // we will switch to the calling user if access to the current user fails.
        @CanBeALL int targetUserId = unsafeConvertIncomingUser(userId);

        if (callingUid != 0 && callingUid != SYSTEM_UID) {
            final boolean allow;
            final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, targetUserId);
            if (mInjector.isCallerRecents(callingUid) && isSameProfileGroup) {
                // If the caller is Recents and the caller has ownership of the profile group,
                // we then allow it to access its profiles.
                allow = true;
            } else if (mInjector.checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid,
                    callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) {
                // If the caller has this permission, they always pass go.  And collect $200.
                allow = true;
            } else if (allowMode == ALLOW_FULL_ONLY) {
                // We require full access, sucks to be you.
                allow = false;
            } else if (canInteractWithAcrossProfilesPermission(
                    allowMode, isSameProfileGroup, callingPid, callingUid, callerPackage)) {
                allow = true;
            } else if (mInjector.checkComponentPermission(INTERACT_ACROSS_USERS, callingPid,
                    callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) {
                // If the caller does not have either permission, they are always doomed.
                allow = false;
            } else if (allowMode == ALLOW_NON_FULL || allowMode == ALLOW_PROFILES_OR_NON_FULL) {
                // We are blanket allowing non-full access, you lucky caller!
                allow = true;
            } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) {
                // We may or may not allow this depending on whether the two users are
                // in the same profile.
                allow = isSameProfileGroup;
            } else {
                throw new IllegalArgumentException("Unknown mode: " + allowMode);
            }
            if (!allow) {
                if (userId == UserHandle.USER_CURRENT_OR_SELF) {
                    // In this case, they would like to just execute as their
                    // owner user instead of failing.
                    targetUserId = callingUserId;
                } else {
                    StringBuilder builder = new StringBuilder(128);
                    builder.append("Permission Denial: ");
                    builder.append(name);
                    if (callerPackage != null) {
                        builder.append(" from ");
                        builder.append(callerPackage);
                    }
                    builder.append(" asks to run as user ");
                    builder.append(userId);
                    builder.append(" but is calling from uid ");
                    UserHandle.formatUid(builder, callingUid);
                    builder.append("; this requires ");
                    builder.append(INTERACT_ACROSS_USERS_FULL);
                    if (allowMode != ALLOW_FULL_ONLY) {
                        if (allowMode == ALLOW_NON_FULL
                                || allowMode == ALLOW_PROFILES_OR_NON_FULL
                                || (allowMode == ALLOW_NON_FULL_IN_PROFILE && isSameProfileGroup)) {
                            builder.append(" or ");
                            builder.append(INTERACT_ACROSS_USERS);
                        }
                        if (isSameProfileGroup && allowMode == ALLOW_PROFILES_OR_NON_FULL) {
                            builder.append(" or ");
                            builder.append(INTERACT_ACROSS_PROFILES);
                        }
                    }
                    String msg = builder.toString();
                    Slogf.w(TAG, msg);
                    throw new SecurityException(msg);
                }
            }
        }
        if (!allowAll) {
            ensureNotSpecialUser(targetUserId);
        }
        // Check shell permission
        if (callingUid == Process.SHELL_UID && targetUserId >= UserHandle.USER_SYSTEM) {
            if (hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, targetUserId)) {
                throw new SecurityException("Shell does not have permission to access user "
                        + targetUserId + "\n " + Debug.getCallers(3));
            }
        }
        return targetUserId;
    }

    private boolean canInteractWithAcrossProfilesPermission(
            int allowMode, boolean isSameProfileGroup, int callingPid, int callingUid,
            String callingPackage) {
        if (allowMode != ALLOW_PROFILES_OR_NON_FULL) {
            return false;
        }
        if (!isSameProfileGroup) {
            return false;
        }
        return mInjector.checkPermissionForPreflight(INTERACT_ACROSS_PROFILES, callingPid,
                callingUid, callingPackage);
    }

    @CanBeALL @UserIdInt int unsafeConvertIncomingUser(
            @CanBeALL @CanBeCURRENT @CanBeCURRENT_OR_SELF @UserIdInt int userId) {
        return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
                ? getCurrentUserId(): userId;
    }

    void ensureNotSpecialUser(@UserIdInt int userId) {
        if (userId >= 0) {
            return;
        }
        throw new IllegalArgumentException("Call does not support special user #" + userId);
    }

    void registerUserSwitchObserver(IUserSwitchObserver observer, String name) {
        Objects.requireNonNull(name, "Observer name cannot be null");
        checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "registerUserSwitchObserver");
        mUserSwitchObservers.register(observer, name);
    }

    void sendForegroundProfileChanged(@UserIdInt int userId) {
        mHandler.removeMessages(FOREGROUND_PROFILE_CHANGED_MSG);
        mHandler.obtainMessage(FOREGROUND_PROFILE_CHANGED_MSG, userId, 0).sendToTarget();
    }

    void unregisterUserSwitchObserver(IUserSwitchObserver observer) {
        mUserSwitchObservers.unregister(observer);
    }

    UserState getStartedUserState(@UserIdInt int userId) {
        synchronized (mLock) {
            return mStartedUsers.get(userId);
        }
    }

    boolean hasStartedUserState(@UserIdInt int userId) {
        synchronized (mLock) {
            return mStartedUsers.get(userId) != null;
        }
    }

    @GuardedBy("mLock")
    private void updateStartedUserArrayLU() {
        int num = 0;
        for (int i = 0; i < mStartedUsers.size(); i++) {
            UserState uss = mStartedUsers.valueAt(i);
            // This list does not include stopping users.
            if (uss.state != UserState.STATE_STOPPING
                    && uss.state != UserState.STATE_SHUTDOWN) {
                num++;
            }
        }
        mStartedUserArray = new int[num];
        num = 0;
        for (int i = 0; i < mStartedUsers.size(); i++) {
            UserState uss = mStartedUsers.valueAt(i);
            if (uss.state != UserState.STATE_STOPPING
                    && uss.state != UserState.STATE_SHUTDOWN) {
                mStartedUserArray[num++] = mStartedUsers.keyAt(i);
            }
        }
    }

    @VisibleForTesting
    void setAllowUserUnlocking(boolean allowed) {
        mAllowUserUnlocking = allowed;
        if (DEBUG_MU) {
            Slogf.d(TAG, new Exception(), "setAllowUserUnlocking(%b)", allowed);
        }
    }

    void onBootComplete(IIntentReceiver resultTo) {
        // Now that PHASE_BOOT_COMPLETED has been reached, user unlocking is allowed.
        setAllowUserUnlocking(true);

        // Get a copy of mStartedUsers to use outside of lock.
        SparseArray<UserState> startedUsers;
        synchronized (mLock) {
            startedUsers = mStartedUsers.clone();
        }
        // In non-headless system user mode, call finishUserBoot() to transition the system user
        // from the BOOTING state to RUNNING_LOCKED, then to RUNNING_UNLOCKED if possible.
        //
        // In headless system user mode, additional users may have been started, and all users
        // (including the system user) that ever get started are started explicitly.  In this case,
        // we should *not* transition users out of the BOOTING state using finishUserBoot(), as that
        // doesn't handle issuing the needed onUserStarting() call, and it would just race with an
        // explicit start anyway.  We do, however, need to send the "locked boot complete" broadcast
        // as that got skipped earlier due to the *device* boot not being complete yet.
        // We also need to try to unlock all started users, since until now explicit user starts
        // didn't proceed to unlocking, due to it being too early in the device boot.
        //
        // USER_SYSTEM must be processed first.  It will be first in the array, as its ID is lowest.
        Preconditions.checkArgument(startedUsers.keyAt(0) == UserHandle.USER_SYSTEM);
        for (int i = 0; i < startedUsers.size(); i++) {
            int userId = startedUsers.keyAt(i);
            UserState uss = startedUsers.valueAt(i);
            if (!mInjector.isHeadlessSystemUserMode()) {
                finishUserBoot(uss, resultTo);
            } else {
                sendLockedBootCompletedBroadcast(resultTo, userId);
                maybeUnlockUser(userId);
            }
        }
    }

    void onSystemReady() {
        if (DEBUG_MU) {
            Slogf.d(TAG, "onSystemReady()");

        }
        mInjector.getUserManagerInternal().addUserLifecycleListener(mUserLifecycleListener);
        updateProfileRelatedCaches();
        mInjector.reportCurWakefulnessUsageEvent();

        // IpcDataCache must be invalidated before it starts caching.
        ActivityManager.invalidateGetCurrentUserIdCache();

        synchronized (mLock) {
            mReady = true;
        }
    }

    // TODO(b/266158156): remove this method if initial system user boot logic is refactored?
    void onSystemUserStarting() {
        if (!mInjector.isHeadlessSystemUserMode()) {
            // Don't need to call on HSUM because it will be called when the system user is
            // restarted on background
            mInjector.onUserStarting(UserHandle.USER_SYSTEM);
            mInjector.onSystemUserVisibilityChanged(/* visible= */ true);
        }
    }

    /**
     * Refreshes the internal caches related to user profiles.
     *
     * <p>It's called every time a user is started.
     */
    private void updateProfileRelatedCaches() {
        final List<UserInfo> profiles = mInjector.getUserManager().getProfiles(getCurrentUserId(),
                /* enabledOnly= */ false);
        int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null
        for (int i = 0; i < currentProfileIds.length; i++) {
            currentProfileIds[i] = profiles.get(i).id;
        }
        final List<UserInfo> users = mInjector.getUserManager().getUsers(false);
        synchronized (mLock) {
            mCurrentProfileIds = currentProfileIds;

            mUserProfileGroupIds.clear();
            for (int i = 0; i < users.size(); i++) {
                UserInfo user = users.get(i);
                if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
                    mUserProfileGroupIds.put(user.id, user.profileGroupId);
                }
            }
        }
    }

    int[] getStartedUserArray() {
        synchronized (mLock) {
            return mStartedUserArray;
        }
    }

    boolean isUserRunning(@UserIdInt int userId, int flags) {
        UserState state = getStartedUserState(userId);
        if (state == null) {
            return false;
        }
        if ((flags & ActivityManager.FLAG_OR_STOPPED) != 0) {
            return true;
        }
        if ((flags & ActivityManager.FLAG_AND_LOCKED) != 0) {
            switch (state.state) {
                case UserState.STATE_BOOTING:
                case UserState.STATE_RUNNING_LOCKED:
                    return true;
                default:
                    return false;
            }
        }
        if ((flags & ActivityManager.FLAG_AND_UNLOCKING_OR_UNLOCKED) != 0) {
            switch (state.state) {
                case UserState.STATE_RUNNING_UNLOCKING:
                case UserState.STATE_RUNNING_UNLOCKED:
                    return true;
                // In the stopping/shutdown state, return unlock state of the user's CE storage.
                case UserState.STATE_STOPPING:
                case UserState.STATE_SHUTDOWN:
                    return StorageManager.isCeStorageUnlocked(userId);
                default:
                    return false;
            }
        }
        if ((flags & ActivityManager.FLAG_AND_UNLOCKED) != 0) {
            switch (state.state) {
                case UserState.STATE_RUNNING_UNLOCKED:
                    return true;
                // In the stopping/shutdown state, return unlock state of the user's CE storage.
                case UserState.STATE_STOPPING:
                case UserState.STATE_SHUTDOWN:
                    return StorageManager.isCeStorageUnlocked(userId);
                default:
                    return false;
            }
        }

        return state.state != UserState.STATE_STOPPING && state.state != UserState.STATE_SHUTDOWN;
    }

    /**
     * Check if system user is already started. Unlike other user, system user is in STATE_BOOTING
     * even if it is not explicitly started. So isUserRunning cannot give the right state
     * to check if system user is started or not.
     * @return true if system user is started.
     */
    boolean isSystemUserStarted() {
        synchronized (mLock) {
            UserState uss = mStartedUsers.get(UserHandle.USER_SYSTEM);
            if (uss == null) {
                return false;
            }
            return uss.state == UserState.STATE_RUNNING_LOCKED
                || uss.state == UserState.STATE_RUNNING_UNLOCKING
                || uss.state == UserState.STATE_RUNNING_UNLOCKED;
        }
    }

    private void checkGetCurrentUserPermissions() {
        if ((mInjector.checkCallingPermission(INTERACT_ACROSS_USERS)
                != PackageManager.PERMISSION_GRANTED) && (
                mInjector.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
                        != PackageManager.PERMISSION_GRANTED)) {
            String msg = "Permission Denial: getCurrentUser() from pid="
                    + Binder.getCallingPid()
                    + ", uid=" + Binder.getCallingUid()
                    + " requires " + INTERACT_ACROSS_USERS;
            Slogf.w(TAG, msg);
            throw new SecurityException(msg);
        }
    }

    UserInfo getCurrentUser() {
        checkGetCurrentUserPermissions();

        // Optimization - if there is no pending user switch, return user for current id
        // (no need to acquire lock because mTargetUserId and mCurrentUserId are volatile)
        if (mTargetUserId == UserHandle.USER_NULL) {
            return getUserInfo(mCurrentUserId);
        }
        synchronized (mLock) {
            return getCurrentUserLU();
        }
    }

    /**
     * Gets the current user id, but checking that caller has the proper permissions.
     */
    int getCurrentUserIdChecked() {
        checkGetCurrentUserPermissions();

        // Optimization - if there is no pending user switch, return current id
        // (no need to acquire lock because mTargetUserId and mCurrentUserId are volatile)
        if (mTargetUserId == UserHandle.USER_NULL) {
            return mCurrentUserId;
        }
        return getCurrentOrTargetUserId();
    }

    @GuardedBy("mLock")
    private UserInfo getCurrentUserLU() {
        int userId = getCurrentOrTargetUserIdLU();
        return getUserInfo(userId);
    }

    int getCurrentOrTargetUserId() {
        synchronized (mLock) {
            return getCurrentOrTargetUserIdLU();
        }
    }

    @GuardedBy("mLock")
    private int getCurrentOrTargetUserIdLU() {
        // Note: this result is currently cached by ActivityManager.getCurrentUser() - changes to
        // the logic here may require updating how the cache is invalidated.
        // See ActivityManager.invalidateGetCurrentUserIdCache() for more details.
        return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
    }

    Pair<Integer, Integer> getCurrentAndTargetUserIds() {
        synchronized (mLock) {
            return new Pair<>(mCurrentUserId, mTargetUserId);
        }
    }

    @GuardedBy("mLock")
    private int getCurrentUserIdLU() {
        return mCurrentUserId;
    }

    int getCurrentUserId() {
        synchronized (mLock) {
            return mCurrentUserId;
        }
    }

    @GuardedBy("mLock")
    private boolean isCurrentUserLU(@UserIdInt int userId) {
        return userId == getCurrentOrTargetUserIdLU();
    }

    /**
     * Returns whether the user is currently visible, including users visible on a background
     * display and always-visible users (e.g. the communal profile).
     *
     * <p>Note that this is whether the user itself is considered a visible user, not merely a user
     * that happens to be displaying a {@link Injector#getVisibleActivityUsers() visible activity}.
     */
    private boolean isUserVisible(@UserIdInt int userId) {
        return mInjector.getUserManagerInternal().isUserVisible(userId);
    }

    int[] getUsers() {
        UserManagerService ums = mInjector.getUserManager();
        return ums != null ? ums.getUserIds() : new int[] { 0 };
    }

    private UserInfo getUserInfo(@UserIdInt int userId) {
        return mInjector.getUserManager().getUserInfo(userId);
    }

    private @Nullable UserProperties getUserProperties(@UserIdInt int userId) {
        return mInjector.getUserManagerInternal().getUserProperties(userId);
    }

    int[] getUserIds() {
        return mInjector.getUserManager().getUserIds();
    }

    /**
     * If {@code userId} is {@link UserHandle#USER_ALL}, then return an array with all running user
     * IDs. Otherwise return an array whose only element is the given user id.
     *
     * It doesn't handle other special user IDs such as {@link UserHandle#USER_CURRENT}.
     */
    int[] expandUserId(@CanBeALL @UserIdInt int userId) {
        if (userId != UserHandle.USER_ALL) {
            return new int[] {userId};
        } else {
            return getUsers();
        }
    }

    boolean exists(@UserIdInt int userId) {
        return mInjector.getUserManager().exists(userId);
    }

    private void checkCallingPermission(String permission, String methodName) {
        checkCallingHasOneOfThosePermissions(methodName, permission);
    }

    private void checkCallingHasOneOfThosePermissions(String methodName, String...permissions) {
        for (String permission : permissions) {
            if (mInjector.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) {
                return;
            }
        }
        String msg = "Permission denial: " + methodName
                + "() from pid=" + Binder.getCallingPid()
                + ", uid=" + Binder.getCallingUid()
                + " requires "
                + (permissions.length == 1
                        ? permissions[0]
                        : "one of " + Arrays.toString(permissions));
        Slogf.w(TAG, msg);
        throw new SecurityException(msg);
    }

    private void enforceShellRestriction(String restriction, @UserIdInt int userId) {
        if (Binder.getCallingUid() == SHELL_UID) {
            if (userId < 0 || hasUserRestriction(restriction, userId)) {
                throw new SecurityException("Shell does not have permission to access user "
                        + userId);
            }
        }
    }

    boolean hasUserRestriction(String restriction, @UserIdInt int userId) {
        return mInjector.getUserManager().hasUserRestriction(restriction, userId);
    }

    /** Returns whether the two users are in the same profile group. */
    boolean isSameProfileGroup(int callingUserId, int targetUserId) {
        if (callingUserId == targetUserId) {
            return true;
        }
        synchronized (mLock) {
            int callingProfile = mUserProfileGroupIds.get(callingUserId,
                    UserInfo.NO_PROFILE_GROUP_ID);
            int targetProfile = mUserProfileGroupIds.get(targetUserId,
                    UserInfo.NO_PROFILE_GROUP_ID);
            return callingProfile != UserInfo.NO_PROFILE_GROUP_ID
                    && callingProfile == targetProfile;
        }
    }

    boolean isUserOrItsParentRunning(@UserIdInt int userId) {
        synchronized (mLock) {
            if (isUserRunning(userId, 0)) {
                return true;
            }
            final int parentUserId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
            if (parentUserId == UserInfo.NO_PROFILE_GROUP_ID) {
                return false;
            }
            return isUserRunning(parentUserId, 0);
        }
    }

    boolean isCurrentProfile(@UserIdInt int userId) {
        synchronized (mLock) {
            return ArrayUtils.contains(mCurrentProfileIds, userId);
        }
    }

    int[] getCurrentProfileIds() {
        synchronized (mLock) {
            return mCurrentProfileIds;
        }
    }

    private void onUserAdded(UserInfo user) {
        if (!user.isProfile()) {
            return;
        }
        synchronized (mLock) {
            if (user.profileGroupId == mCurrentUserId) {
                mCurrentProfileIds = ArrayUtils.appendInt(mCurrentProfileIds, user.id);
            }
            if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
                mUserProfileGroupIds.put(user.id, user.profileGroupId);
            }
        }
    }

    void onUserRemoved(@UserIdInt int userId) {
        synchronized (mLock) {
            int size = mUserProfileGroupIds.size();
            for (int i = size - 1; i >= 0; i--) {
                if (mUserProfileGroupIds.keyAt(i) == userId
                        || mUserProfileGroupIds.valueAt(i) == userId) {
                    mUserProfileGroupIds.removeAt(i);
                }
            }
            mCurrentProfileIds = ArrayUtils.removeInt(mCurrentProfileIds, userId);
            mUserLru.remove((Integer) userId);
            mStartedUsers.remove(userId);
            updateStartedUserArrayLU();
        }
    }

    /**
     * Returns whether the given user requires credential entry at this time. This is used to
     * intercept activity launches for apps corresponding to locked profiles due to separate
     * challenge being triggered or when the profile user is yet to be unlocked.
     */
    protected boolean shouldConfirmCredentials(@UserIdInt int userId) {
        if (getStartedUserState(userId) == null) {
            return false;
        }
        final UserProperties properties = getUserProperties(userId);
        if (properties == null || !properties.isCredentialShareableWithParent()) {
            return false;
        }
        if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
            final KeyguardManager km = mInjector.getKeyguardManager();
            return km.isDeviceLocked(userId) && km.isDeviceSecure(userId);
        } else {
            // For unified challenge, need to confirm credential if user is RUNNING_LOCKED.
            return isUserRunning(userId, ActivityManager.FLAG_AND_LOCKED);
        }
    }

    boolean isLockScreenDisabled(@UserIdInt int userId) {
        return mLockPatternUtils.isLockScreenDisabled(userId);
    }

    void setSwitchingFromUserMessage(@UserIdInt int user, @Nullable String message) {
        synchronized (mLock) {
            mSwitchingFromUserMessage.put(user, message);
        }
    }

    void setSwitchingToUserMessage(@UserIdInt int user, @Nullable String message) {
        synchronized (mLock) {
            mSwitchingToUserMessage.put(user, message);
        }
    }

    // Called by AMS, must check permission
    @Nullable String getSwitchingFromUserMessage(@UserIdInt int userId) {
        checkHasManageUsersPermission("getSwitchingFromUserMessage()");

        return getSwitchingFromUserMessageUnchecked(userId);
    }

    // Called by AMS, must check permission
    @Nullable String getSwitchingToUserMessage(@UserIdInt int userId) {
        checkHasManageUsersPermission("getSwitchingToUserMessage()");

        return getSwitchingToUserMessageUnchecked(userId);
    }

    private @Nullable String getSwitchingFromUserMessageUnchecked(@UserIdInt int userId) {
        synchronized (mLock) {
            return mSwitchingFromUserMessage.get(userId);
        }
    }

    private @Nullable String getSwitchingToUserMessageUnchecked(@UserIdInt int userId) {
        synchronized (mLock) {
            return mSwitchingToUserMessage.get(userId);
        }
    }

    private void checkHasManageUsersPermission(String operation) {
        if (mInjector.checkCallingPermission(
                android.Manifest.permission.MANAGE_USERS) == PackageManager.PERMISSION_DENIED) {
            throw new SecurityException(
                    "You need MANAGE_USERS permission to call " + operation);
        }
    }

    void dumpDebug(ProtoOutputStream proto, long fieldId) {
        synchronized (mLock) {
            long token = proto.start(fieldId);
            for (int i = 0; i < mStartedUsers.size(); i++) {
                UserState uss = mStartedUsers.valueAt(i);
                final long uToken = proto.start(UserControllerProto.STARTED_USERS);
                proto.write(UserControllerProto.User.ID, uss.mHandle.getIdentifier());
                uss.dumpDebug(proto, UserControllerProto.User.STATE);
                proto.end(uToken);
            }
            for (int i = 0; i < mStartedUserArray.length; i++) {
                proto.write(UserControllerProto.STARTED_USER_ARRAY, mStartedUserArray[i]);
            }
            for (int i = 0; i < mUserLru.size(); i++) {
                proto.write(UserControllerProto.USER_LRU, mUserLru.get(i));
            }
            if (mUserProfileGroupIds.size() > 0) {
                for (int i = 0; i < mUserProfileGroupIds.size(); i++) {
                    final long uToken = proto.start(UserControllerProto.USER_PROFILE_GROUP_IDS);
                    proto.write(UserControllerProto.UserProfile.USER,
                            mUserProfileGroupIds.keyAt(i));
                    proto.write(UserControllerProto.UserProfile.PROFILE,
                            mUserProfileGroupIds.valueAt(i));
                    proto.end(uToken);
                }
            }
            proto.write(UserControllerProto.CURRENT_USER, mCurrentUserId);
            for (int i = 0; i < mCurrentProfileIds.length; i++) {
                proto.write(UserControllerProto.CURRENT_PROFILES, mCurrentProfileIds[i]);
            }
            proto.write(UserControllerProto.READY, mReady);
            proto.end(token);
        }
    }

    void dump(PrintWriter pw) {
        synchronized (mLock) {
            pw.println("  mStartedUsers:");
            for (int i = 0; i < mStartedUsers.size(); i++) {
                UserState uss = mStartedUsers.valueAt(i);
                pw.print("    User #");
                pw.print(uss.mHandle.getIdentifier());
                pw.print(": ");
                uss.dump("", pw);
            }
            pw.print("  mStartedUserArray: [");
            for (int i = 0; i < mStartedUserArray.length; i++) {
                if (i > 0)
                    pw.print(", ");
                pw.print(mStartedUserArray[i]);
            }
            pw.println("]");
            pw.print("  mUserLru: [");
            for (int i = 0; i < mUserLru.size(); i++) {
                if (i > 0)
                    pw.print(", ");
                pw.print(mUserLru.get(i));
            }
            pw.println("]");
            if (mUserProfileGroupIds.size() > 0) {
                pw.println("  mUserProfileGroupIds:");
                for (int i=0; i< mUserProfileGroupIds.size(); i++) {
                    pw.print("    User #");
                    pw.print(mUserProfileGroupIds.keyAt(i));
                    pw.print(" -> profile #");
                    pw.println(mUserProfileGroupIds.valueAt(i));
                }
            }
            pw.println("  mCurrentProfileIds:" + Arrays.toString(mCurrentProfileIds));
            pw.println("  mCurrentUserId:" + mCurrentUserId);
            pw.println("  mTargetUserId:" + mTargetUserId);
            pw.println("  mPendingTargetUserIds:" + mPendingTargetUserIds);
            pw.println("  mPendingLogoutTargetUser:" + mPendingLogoutTargetUser);
            pw.println("  mLastActiveUsersForDelayedLocking:" + mLastActiveUsersForDelayedLocking);
            pw.println("  mDelayUserDataLocking:" + mDelayUserDataLocking);
            pw.println("  mAllowUserUnlocking:" + mAllowUserUnlocking);
            pw.println("  isStopUserOnSwitchEnabled():" + isStopUserOnSwitchEnabled());
            pw.println("  mStopUserOnSwitch:" + mStopUserOnSwitch);
            pw.println("  mMaxRunningUsers:" + mMaxRunningUsers);
            pw.println("  mBackgroundUserConsideredDispensableTimeSecs:"
                    + mBackgroundUserConsideredDispensableTimeSecs);
            pw.println("  mUserSwitchUiEnabled:" + mUserSwitchUiEnabled);
            pw.println("  mInitialized:" + mInitialized);
            pw.println("  mIsBroadcastSentForSystemUserStarted:"
                    + mIsBroadcastSentForSystemUserStarted);
            pw.println("  mIsBroadcastSentForSystemUserStarting:"
                    + mIsBroadcastSentForSystemUserStarting);
            pw.println("  mSwitchingFromUserMessage:" + mSwitchingFromUserMessage);
            pw.println("  mSwitchingToUserMessage:" + mSwitchingToUserMessage);
            pw.println("  mLastUserUnlockingUptime: " + mLastUserUnlockingUptime);
            pw.println("  mReady: " + mReady);
        }
    }

    @Override
    public boolean handleMessage(Message msg) {
        switch (msg.what) {
            case START_USER_SWITCH_FG_MSG:
                logUserJourneyBegin(msg.arg1, USER_JOURNEY_USER_SWITCH_FG);
                startUserInForeground(msg.arg1);
                break;
            case REPORT_USER_SWITCH_MSG:
                dispatchUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
                break;
            case CONTINUE_USER_SWITCH_MSG:
                continueUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
                break;
            case USER_SWITCH_TIMEOUT_MSG:
                timeoutUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
                break;
            case USER_SWITCH_CALLBACKS_TIMEOUT_MSG:
                timeoutUserSwitchCallbacks(msg.arg1, msg.arg2);
                break;
            case START_PROFILES_MSG:
                startProfiles();
                break;
            case USER_START_MSG:
                mInjector.batteryStatsServiceNoteEvent(
                        BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
                        Integer.toString(msg.arg1), msg.arg1);
                logUserJourneyBegin(msg.arg1, USER_JOURNEY_USER_START);

                mInjector.onUserStarting(/* userId= */ msg.arg1);
                scheduleOnUserCompletedEvent(msg.arg1,
                        UserCompletedEventType.EVENT_TYPE_USER_STARTING,
                        USER_COMPLETED_EVENT_DELAY_MS);

                mInjector.getUserJourneyLogger().logUserJourneyFinish(-1 , getUserInfo(msg.arg1),
                        USER_JOURNEY_USER_START);
                break;
            case USER_UNLOCK_MSG:
                final int userId = msg.arg1;
                mInjector.getSystemServiceManager().onUserUnlocking(userId);
                // Loads recents on a worker thread that allows disk I/O
                FgThread.getHandler().post(() -> {
                    mInjector.loadUserRecents(userId);
                });

                mInjector.getUserJourneyLogger().logUserLifecycleEvent(msg.arg1,
                        USER_LIFECYCLE_EVENT_UNLOCKING_USER, EVENT_STATE_FINISH);
                mInjector.getUserJourneyLogger().logUserLifecycleEvent(msg.arg1,
                        USER_LIFECYCLE_EVENT_UNLOCKED_USER, EVENT_STATE_BEGIN);

                final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
                t.traceBegin("finishUserUnlocked-" + userId);
                finishUserUnlocked((UserState) msg.obj);
                t.traceEnd();
                break;
            case USER_UNLOCKED_MSG:
                mInjector.getSystemServiceManager().onUserUnlocked(msg.arg1);
                scheduleOnUserCompletedEvent(msg.arg1,
                        UserCompletedEventType.EVENT_TYPE_USER_UNLOCKED,
                        // If it's the foreground user, we wait longer to let it fully load.
                        // Else, there's nothing specific to wait for, so we basically just proceed.
                        // (No need to acquire lock to read mCurrentUserId since it is volatile.)
                        // TODO: Find something to wait for in the case of a profile.
                        mCurrentUserId == msg.arg1 ? USER_COMPLETED_EVENT_DELAY_MS : 1000);
                mInjector.getUserJourneyLogger().logUserLifecycleEvent(msg.arg1,
                        USER_LIFECYCLE_EVENT_UNLOCKED_USER, EVENT_STATE_FINISH);
                // Unlocking user is not a journey no need to clear sessionId
                break;
            case USER_CURRENT_MSG:
                mInjector.batteryStatsServiceNoteEvent(
                        BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_FINISH,
                        Integer.toString(msg.arg2), msg.arg2);
                mInjector.batteryStatsServiceNoteEvent(
                        BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
                        Integer.toString(msg.arg1), msg.arg1);

                mInjector.getSystemServiceManager().onUserSwitching(msg.arg2, msg.arg1);
                stopPreviousUserPackagesIfEnabled(msg.arg2, msg.arg1);
                scheduleOnUserCompletedEvent(msg.arg1,
                        UserCompletedEventType.EVENT_TYPE_USER_SWITCHING,
                        USER_COMPLETED_EVENT_DELAY_MS);
                break;
            case USER_COMPLETED_EVENT_MSG:
                reportOnUserCompletedEvent((Integer) msg.obj);
                break;
            case FOREGROUND_PROFILE_CHANGED_MSG:
                dispatchForegroundProfileChanged(msg.arg1);
                break;
            case REPORT_USER_SWITCH_COMPLETE_MSG:
                dispatchUserSwitchComplete(msg.arg1, msg.arg2);
                UserJourneySession session = mInjector.getUserJourneyLogger()
                        .logUserSwitchJourneyFinish(msg.arg1, getUserInfo(msg.arg2));
                if (session != null) {
                    mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, session);
                }
                break;
            case REPORT_LOCKED_BOOT_COMPLETE_MSG:
                dispatchLockedBootComplete(msg.arg1);
                break;
            case START_USER_SWITCH_UI_MSG:
                final Pair<UserInfo, UserInfo> fromToUserPair = (Pair<UserInfo, UserInfo>) msg.obj;
                logUserJourneyBegin(fromToUserPair.second.id, USER_JOURNEY_USER_SWITCH_UI);
                showUserSwitchDialog(fromToUserPair);
                break;
            case CLEAR_USER_JOURNEY_SESSION_MSG:
                mInjector.getUserJourneyLogger()
                        .finishAndClearIncompleteUserJourney(msg.arg1, msg.arg2);
                mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, msg.obj);
                break;
            case COMPLETE_USER_SWITCH_MSG:
                completeUserSwitch(msg.arg1, msg.arg2);
                break;
            case SCHEDULE_STOP_BACKGROUND_USER_MSG:
                processScheduledStopOfBackgroundUser((Integer) msg.obj);
                break;
            case SCHEDULE_JUDGE_FATE_OF_BACKGROUND_USER_MSG:
                processJudgeFateOfBackgroundUser((UserAndLmkThreshold) msg.obj);
                break;
        }
        return false;
    }

    /**
     * Schedules {@link SystemServiceManager#onUserCompletedEvent()} with the given
     * {@link UserCompletedEventType} event, which will be combined with any other events for that
     * user already scheduled.
     * If it isn't rescheduled first, it will fire after a delayMs delay.
     *
     * @param eventType event type flags from {@link UserCompletedEventType} to append to the
     *                  schedule. Use 0 to schedule the ssm call without modifying the event types.
     */
    // TODO(b/197344658): Also call scheduleOnUserCompletedEvent(userId, 0, 0) after switch UX done.
    @VisibleForTesting
    void scheduleOnUserCompletedEvent(
            int userId, @UserCompletedEventType.EventTypesFlag int eventType, int delayMs) {

        if (eventType != 0) {
            synchronized (mCompletedEventTypes) {
                mCompletedEventTypes.put(userId, mCompletedEventTypes.get(userId, 0) | eventType);
            }
        }

        final Object msgObj = userId;
        mHandler.removeEqualMessages(USER_COMPLETED_EVENT_MSG, msgObj);
        mHandler.sendMessageDelayed(
                mHandler.obtainMessage(USER_COMPLETED_EVENT_MSG, msgObj),
                delayMs);
    }

    /**
     * Calls {@link SystemServiceManager#onUserCompletedEvent()} for the given user, sending all the
     * {@link UserCompletedEventType} events that have been scheduled for it if they are still
     * applicable.
     *
     * Called on the mHandler thread.
     */
    @VisibleForTesting
    void reportOnUserCompletedEvent(Integer userId) {
        mHandler.removeEqualMessages(USER_COMPLETED_EVENT_MSG, userId);

        int eventTypes;
        synchronized (mCompletedEventTypes) {
            eventTypes = mCompletedEventTypes.get(userId, 0);
            mCompletedEventTypes.delete(userId);
        }

        // Now, remove any eventTypes that are no longer true.
        int eligibleEventTypes = 0;
        synchronized (mLock) {
            final UserState uss = mStartedUsers.get(userId);
            if (uss != null && uss.state != UserState.STATE_SHUTDOWN) {
                eligibleEventTypes |= UserCompletedEventType.EVENT_TYPE_USER_STARTING;
            }
            if (uss != null && uss.state == STATE_RUNNING_UNLOCKED) {
                eligibleEventTypes |= UserCompletedEventType.EVENT_TYPE_USER_UNLOCKED;
            }
            if (userId == mCurrentUserId) {
                eligibleEventTypes |= UserCompletedEventType.EVENT_TYPE_USER_SWITCHING;
            }
        }
        Slogf.i(TAG, "reportOnUserCompletedEvent(%d): stored=%s, eligible=%s", userId,
                Integer.toBinaryString(eventTypes), Integer.toBinaryString(eligibleEventTypes));
        eventTypes &= eligibleEventTypes;

        mInjector.systemServiceManagerOnUserCompletedEvent(userId, eventTypes);
    }

    /**
     * statsd helper method for logging the start of a user journey via a UserLifecycleEventOccurred
     * atom given the originating and targeting users for the journey.
     */
    private void logUserJourneyBegin(int targetId,
            @UserJourneyLogger.UserJourney int journey) {
        UserJourneySession oldSession = mInjector.getUserJourneyLogger()
                    .finishAndClearIncompleteUserJourney(targetId, journey);
        if (oldSession != null) {
            if (DEBUG_MU) {
                Slogf.d(TAG,
                        "Starting a new journey: " + journey + " with session id: "
                                + oldSession);
            }
            /*
             * User lifecycle journey would be complete when {@code #clearSessionId} is called
             * after the last expected lifecycle event for the journey. It may be possible that
             * the last event is not called, e.g., user not unlocked after user switching. In such
             * cases user journey is cleared after {@link USER_JOURNEY_TIMEOUT}.
             */
            mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, oldSession);
        }
        UserJourneySession newSession = mInjector.getUserJourneyLogger()
                .logUserJourneyBegin(targetId, journey);
        mHandler.sendMessageDelayed(mHandler.obtainMessage(CLEAR_USER_JOURNEY_SESSION_MSG,
                targetId, /* arg2= */ journey, newSession), USER_JOURNEY_TIMEOUT_MS);

    }

    BroadcastOptions getTemporaryAppAllowlistBroadcastOptions(
            @PowerWhitelistManager.ReasonCode int reasonCode) {
        long duration = 10_000;
        final ActivityManagerInternal amInternal =
                LocalServices.getService(ActivityManagerInternal.class);
        if (amInternal != null) {
            duration = amInternal.getBootTimeTempAllowListDuration();
        }
        final BroadcastOptions bOptions = BroadcastOptions.makeBasic();
        bOptions.setTemporaryAppAllowlist(duration,
                TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, reasonCode, "");
        return bOptions;
    }

    private static int getUserSwitchTimeoutMs() {
        final String userSwitchTimeoutMs = SystemProperties.get(
                "debug.usercontroller.user_switch_timeout_ms");
        if (!TextUtils.isEmpty(userSwitchTimeoutMs)) {
            try {
                return Integer.parseInt(userSwitchTimeoutMs);
            } catch (NumberFormatException ignored) {
                // Ignored.
            }
        }
        return DEFAULT_USER_SWITCH_TIMEOUT_MS;
    }

    private static void asyncTraceBegin(String msg, int cookie) {
        Slogf.d(TAG, "%s - asyncTraceBegin(%d)", msg, cookie);
        Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, msg, cookie);
    }

    private static void asyncTraceEnd(String msg, int cookie) {
        Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, msg, cookie);
        Slogf.d(TAG, "%s - asyncTraceEnd(%d)", msg, cookie);
    }

    /**
     * Uptime when any user was being unlocked most recently. 0 if no users have been unlocked
     * yet. To avoid lock contention (since it's used by OomAdjuster), it's volatile internally.
     */
    public long getLastUserUnlockingUptime() {
        return mLastUserUnlockingUptime;
    }

    private static class UserProgressListener extends IProgressListener.Stub {
        private volatile long mUnlockStarted;
        @Override
        public void onStarted(int id, Bundle extras) throws RemoteException {
            Slogf.d(TAG, "Started unlocking user " + id);
            mUnlockStarted = SystemClock.uptimeMillis();
        }

        @Override
        public void onProgress(int id, int progress, Bundle extras) throws RemoteException {
            Slogf.d(TAG, "Unlocking user " + id + " progress " + progress);
        }

        @Override
        public void onFinished(int id, Bundle extras) throws RemoteException {
            long unlockTime = SystemClock.uptimeMillis() - mUnlockStarted;

            // Report system user unlock time to perf dashboard
            if (id == UserHandle.USER_SYSTEM) {
                new TimingsTraceAndSlog().logDuration("SystemUserUnlock", unlockTime);
            } else {
                new TimingsTraceAndSlog().logDuration("User" + id + "Unlock", unlockTime);
            }
        }
    }

    /**
     * Helper class for keeping track of user starts which are paused while user's
     * shutdown is taking place.
     */
    private static class PendingUserStart {
        public final @UserIdInt int userId;
        public final @UserStartMode int userStartMode;
        public final int autoStopUserInSecs;
        public final IProgressListener unlockListener;

        PendingUserStart(int userId, @UserStartMode int userStartMode, int autoStopUserInSecs,
                IProgressListener unlockListener) {
            this.userId = userId;
            this.userStartMode = userStartMode;
            this.autoStopUserInSecs = autoStopUserInSecs;
            this.unlockListener = unlockListener;
        }

        @Override
        public String toString() {
            return "PendingUserStart{"
                    + "userId=" + userId
                    + ", userStartMode=" + userStartModeToString(userStartMode)
                    + ", autoStopUserInSecs=" + autoStopUserInSecs
                    + ", unlockListener=" + unlockListener
                    + '}';
        }
    }

    /**
     * A class holding a userId and an lmkCountThreshold.
     * <p>
     * This is basically just a Pair, except that the equality of two UserAndLmkThreshold objects is
     * determined *only* by the userId, ignoring the lmkCountThreshold. This means that, when used
     * as the obj in {@code mHandler.removeEqualMessage()}, all messages with for the same userId
     * will be purposefully removed, regardless of the lmkCountThreshold values.
     */
    @VisibleForTesting
    static class UserAndLmkThreshold {
        public @UserIdInt int userId;
        public int lmkCountThreshold;

        public UserAndLmkThreshold(int userId, int lmkCountThreshold) {
            this.userId = userId;
            this.lmkCountThreshold = lmkCountThreshold;
        }

        /**
         * Two UserAndLmkThreshold objects are considered equal if and only if their {@code userId}
         * fields are equal. The {@code lmkCountThreshold} is not considered in this comparison.
         */
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            return userId == ((UserAndLmkThreshold) o).userId;
        }

        @Override
        public int hashCode() {
            // Must use the same fields as equals() to ensure that equal objects have the same hash!
            return Objects.hash(userId);
        }

        @Override
        public String toString() {
            return "UserAndLmkThreshold{userId=" + userId
                    + ", lmkCountThreshold=" + lmkCountThreshold + "}";
        }
    }

    @VisibleForTesting
    static class Injector {
        private final ActivityManagerService mService;
        private UserManagerService mUserManager;
        private UserManagerInternal mUserManagerInternal;
        private LockSettingsInternal mLockSettingsInternal;
        private PowerManagerInternal mPowerManagerInternal;
        private Handler mHandler;
        private final Object mUserSwitchingDialogLock = new Object();
        @GuardedBy("mUserSwitchingDialogLock")
        private UserSwitchingDialog mUserSwitchingDialog;

        Injector(ActivityManagerService service) {
            mService = service;
        }

        protected Handler getHandler(Handler.Callback callback) {
            return mHandler = new Handler(mService.mHandlerThread.getLooper(), callback);
        }

        protected Handler getUiHandler(Handler.Callback callback) {
            return new Handler(mService.mUiHandler.getLooper(), callback);
        }

        protected UserJourneyLogger getUserJourneyLogger() {
            return getUserManager().getUserJourneyLogger();
        }

        protected Context getContext() {
            return mService.mContext;
        }

        protected LockPatternUtils getLockPatternUtils() {
            return new LockPatternUtils(getContext());
        }

        protected int broadcastIntent(Intent intent, String resolvedType,
                IIntentReceiver resultTo, int resultCode, String resultData,
                Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
                boolean sticky, int callingPid, int callingUid, int realCallingUid,
                int realCallingPid, @UserIdInt int userId) {

            int logUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
            if (logUserId == UserHandle.USER_NULL) {
                logUserId = userId;
            }
            EventLog.writeEvent(EventLogTags.UC_SEND_USER_BROADCAST, logUserId, intent.getAction());

            TimingsTraceAndSlog t = new TimingsTraceAndSlog();
            // TODO b/64165549 Verify that mLock is not held before calling AMS methods
            synchronized (mService) {
                t.traceBegin("broadcastIntent-" + userId + "-" + intent.getAction());
                final int result = mService.broadcastIntentLocked(null, null, null, intent,
                        resolvedType, resultTo, resultCode, resultData, resultExtras,
                        requiredPermissions, null, null, appOp, bOptions, false, sticky,
                        callingPid, callingUid, realCallingUid, realCallingPid, userId);
                t.traceEnd();
                return result;
            }
        }

        int checkCallingPermission(String permission) {
            return mService.checkCallingPermission(permission);
        }

        WindowManagerService getWindowManager() {
            return mService.mWindowManager;
        }

        void activityManagerOnUserStopped(@UserIdInt int userId) {
            LocalServices.getService(ActivityTaskManagerInternal.class).onUserStopped(userId);
        }

        void systemServiceManagerOnUserStopped(@UserIdInt int userId) {
            getSystemServiceManager().onUserStopped(userId);
        }

        void systemServiceManagerOnUserCompletedEvent(@UserIdInt int userId, int eventTypes) {
            getSystemServiceManager().onUserCompletedEvent(userId, eventTypes);
        }

        protected UserManagerService getUserManager() {
            if (mUserManager == null) {
                IBinder b = ServiceManager.getService(Context.USER_SERVICE);
                mUserManager = (UserManagerService) IUserManager.Stub.asInterface(b);
            }
            return mUserManager;
        }

        UserManagerInternal getUserManagerInternal() {
            if (mUserManagerInternal == null) {
                mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
            }
            return mUserManagerInternal;
        }

        LockSettingsInternal getLockSettingsInternal() {
            if (mLockSettingsInternal == null) {
                mLockSettingsInternal = LocalServices.getService(LockSettingsInternal.class);
            }
            return mLockSettingsInternal;
        }

        PowerManagerInternal getPowerManagerInternal() {
            if (mPowerManagerInternal == null) {
                mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
            }
            return mPowerManagerInternal;
        }

        AudioManagerInternal getAudioManagerInternal() {
            return LocalServices.getService(AudioManagerInternal.class);
        }

        AlarmManagerInternal getAlarmManagerInternal() {
            return LocalServices.getService(AlarmManagerInternal.class);
        }

        KeyguardManager getKeyguardManager() {
            return mService.mContext.getSystemService(KeyguardManager.class);
        }

        void batteryStatsServiceNoteEvent(int code, String name, int uid) {
            mService.mBatteryStatsService.noteEvent(code, name, uid);
        }

        boolean isRuntimeRestarted() {
            return getSystemServiceManager().isRuntimeRestarted();
        }

        SystemServiceManager getSystemServiceManager() {
            return mService.mSystemServiceManager;
        }

        boolean isFirstBootOrUpgrade() {
            IPackageManager pm = AppGlobals.getPackageManager();
            try {
                return pm.isFirstBoot() || pm.isDeviceUpgrading();
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

        void sendPreBootBroadcast(@UserIdInt int userId, boolean quiet, final Runnable onFinish) {
            EventLog.writeEvent(EventLogTags.UC_SEND_USER_BROADCAST,
                    userId, Intent.ACTION_PRE_BOOT_COMPLETED);
            new PreBootBroadcaster(mService, userId, null, quiet) {
                @Override
                public void onFinished() {
                    onFinish.run();
                }
            }.sendNext();
        }

        void activityManagerForceStopUserPackages(@UserIdInt int userId, String reason,
                boolean evenImportantServices) {
            synchronized (mService) {
                mService.forceStopUserPackagesLocked(userId, reason, evenImportantServices);
            }
        };

        int checkComponentPermission(String permission, int pid, int uid, int owningUid,
                boolean exported) {
            return mService.checkComponentPermission(permission, pid, uid, owningUid, exported);
        }

        boolean checkPermissionForPreflight(String permission, int pid, int uid, String pkg) {
            return  PermissionChecker.PERMISSION_GRANTED
                    == PermissionChecker.checkPermissionForPreflight(
                            getContext(), permission, pid, uid, pkg);
        }

        protected void startHomeActivity(@UserIdInt int userId, String reason) {
            mService.mAtmInternal.startHomeActivity(userId, reason);
        }

        void startUserWidgets(@UserIdInt int userId) {
            AppWidgetManagerInternal awm = LocalServices.getService(AppWidgetManagerInternal.class);
            if (awm != null) {
                // Out of band, because this is called during a sequence with
                // sensitive cross-service lock management
                FgThread.getHandler().post(() -> {
                    awm.unlockUser(userId);
                });
            }
        }

        void updateUserConfiguration() {
            mService.mAtmInternal.updateUserConfiguration();
        }

        void clearBroadcastQueueForUser(@UserIdInt int userId) {
            synchronized (mService) {
                mService.clearBroadcastQueueForUserLocked(userId);
            }
        }

        void loadUserRecents(@UserIdInt int userId) {
            mService.mAtmInternal.loadRecentTasksForUser(userId);
        }

        void startPersistentApps(int matchFlags) {
            mService.startPersistentApps(matchFlags);
        }

        void installEncryptionUnawareProviders(@UserIdInt int userId) {
            mService.mCpHelper.installEncryptionUnawareProviders(userId);
        }

        void dismissUserSwitchingDialog(@Nullable Runnable onDismissed) {
            synchronized (mUserSwitchingDialogLock) {
                if (mUserSwitchingDialog != null) {
                    mUserSwitchingDialog.dismiss(onDismissed);
                    mUserSwitchingDialog = null;
                } else if (onDismissed != null) {
                    onDismissed.run();
                }
            }
        }

        void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser,
                @Nullable String switchingFromUserMessage, @Nullable String switchingToUserMessage,
                @NonNull Runnable onShown) {
            if (mService.mContext.getPackageManager()
                    .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
                // config_customUserSwitchUi is set to true on Automotive as CarSystemUI is
                // responsible to show the UI; OEMs should not change that, but if they do, we
                // should at least warn the user...
                Slogf.w(TAG, "Showing user switch dialog on UserController, it could cause a race "
                        + "condition if it's shown by CarSystemUI as well");
            }
            synchronized (mUserSwitchingDialogLock) {
                dismissUserSwitchingDialog(null);
                mUserSwitchingDialog = new UserSwitchingDialog(mService.mContext, fromUser, toUser,
                        mHandler, switchingFromUserMessage, switchingToUserMessage);
                mUserSwitchingDialog.show(onShown);
            }
        }

        void reportGlobalUsageEvent(int event) {
            mService.reportGlobalUsageEvent(event);
        }

        void reportCurWakefulnessUsageEvent() {
            mService.reportCurWakefulnessUsageEvent();
        }

        void taskSupervisorRemoveUser(@UserIdInt int userId) {
            mService.mAtmInternal.removeUser(userId);
        }

        protected boolean taskSupervisorSwitchUser(@UserIdInt int userId, UserState uss) {
            return mService.mAtmInternal.switchUser(userId, uss);
        }

        protected void taskSupervisorResumeFocusedStackTopActivity() {
            mService.mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
        }

        protected void clearAllLockedTasks(String reason) {
            mService.mAtmInternal.clearLockedTasks(reason);
        }

        boolean isCallerRecents(int callingUid) {
            return mService.mAtmInternal.isCallerRecents(callingUid);
        }

        boolean isHeadlessSystemUserMode() {
            return UserManager.isHeadlessSystemUserMode();
        }

        boolean doesUserSupportSwitchTo(UserInfo user) {
            return user.supportsSwitchTo();
        }

        boolean isUsersOnSecondaryDisplaysEnabled() {
            return UserManager.isVisibleBackgroundUsersEnabled();
        }

        void onUserStarting(@UserIdInt int userId) {
            getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId);
        }

        void setPerformancePowerMode(boolean enabled) {
            Slogf.i(TAG, "Setting power mode MODE_FIXED_PERFORMANCE to " + enabled);
            getPowerManagerInternal().setPowerMode(
                    PowerManagerInternal.MODE_FIXED_PERFORMANCE, enabled);
        }

        void onSystemUserVisibilityChanged(boolean visible) {
            getUserManagerInternal().onSystemUserVisibilityChanged(visible);
        }

        void lockDeviceNowAndWaitForKeyguardShown() {
            final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
            t.traceBegin("lockDeviceNowAndWaitForKeyguardShown");

            final CountDownLatch latch = new CountDownLatch(1);
            Bundle bundle = new Bundle();
            bundle.putBinder(LOCK_ON_USER_SWITCH_CALLBACK, new IRemoteCallback.Stub() {
                public void sendResult(Bundle data) {
                    latch.countDown();
                }
            });
            getWindowManager().lockNow(bundle);
            try {
                if (!latch.await(20, TimeUnit.SECONDS)) {
                    throw new RuntimeException("User controller expected a callback while waiting "
                            + "to show the keyguard. Timed out after 20 seconds.");
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                t.traceEnd();
            }
        }

        /**
         * Returns the set of users that are currently displaying visible activities.
         *
         * <p>Even a non-visible background user could be visibly displaying an activity in special
         * circumstances, such as if it is running an app with
         * {@link android.content.pm.ActivityInfo#FLAG_SHOW_FOR_ALL_USERS}.
         */
        ArraySet<Integer> getVisibleActivityUsers() {
            if (!android.multiuser.Flags.rescheduleStopIfVisibleActivities()) {
                return new ArraySet<>();
            }
            ActivityTaskManagerInternal atmi
                    = LocalServices.getService(ActivityTaskManagerInternal.class);
            final ArraySet<Integer> visibleActivityUsers = new ArraySet<>();
            if (atmi != null) {
                for (ActivityAssistInfo info : atmi.getTopVisibleActivities()) {
                    visibleActivityUsers.add(info.getUserId());
                }
            }
            return visibleActivityUsers;
        }

        /**
         * Returns the number of more-important-than-cached low memory kills that have occurred.
         * Returns -1 if the value is unavailable.
         */
        int getLmkdKillCount() {
            final Integer lmk = ProcessList.getLmkdKillCount(0, ProcessList.CACHED_APP_MIN_ADJ - 1);
            return lmk != null && lmk >= 0 ? lmk : -1;
        }

    }
}
