/*
 * 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_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
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.os.Process.SHELL_UID;
import static android.os.Process.SYSTEM_UID;

import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.ActivityManagerService.ALLOW_FULL_ONLY;
import static com.android.server.am.ActivityManagerService.ALLOW_NON_FULL;
import static com.android.server.am.ActivityManagerService.ALLOW_NON_FULL_IN_PROFILE;
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 android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.Dialog;
import android.app.IStopUserCallback;
import android.app.IUserSwitchObserver;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Build;
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.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManagerInternal;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
import android.text.format.DateUtils;
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.TimingsTraceLog;
import android.util.proto.ProtoOutputStream;

import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemServiceManager;
import com.android.server.am.proto.UserControllerProto;
import com.android.server.pm.UserManagerService;
import com.android.server.wm.WindowManagerService;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 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 = TAG_WITH_CLASS_NAME ? "UserController" : TAG_AM;

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

    // 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 SYSTEM_USER_START_MSG = 50;
    static final int SYSTEM_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 SYSTEM_USER_UNLOCK_MSG = 100;
    static final int REPORT_LOCKED_BOOT_COMPLETE_MSG = 110;
    static final int START_USER_SWITCH_FG_MSG = 120;

    // 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
    // USER_SWITCH_TIMEOUT_MS, 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;

    /**
     * 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 a
     * new user. It has no impact on {@link #startUser(int, boolean)} behavior.
     *
     * <p>Note: Current and system user (and their related profiles) are never stopped when
     * switching users. Due to that, the actual number of running users can exceed mMaxRunningUsers
     */
    int mMaxRunningUsers;

    // 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;

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

    /**
     * LRU list of history of current users.  Most recently current is at the end.
     */
    @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 };

    // If there are multiple profiles for the current user, their ids are here
    // Currently only the primary user can have managed profiles
    @GuardedBy("mLock")
    private int[] mCurrentProfileIds = new int[] {};

    /**
     * Mapping from each known user ID to the profile group ID it is associated with.
     */
    @GuardedBy("mLock")
    private final SparseIntArray mUserProfileGroupIds = new SparseIntArray();

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

    boolean mUserSwitchUiEnabled = true;

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

    /**
     * Messages for for switching from {@link android.os.UserHandle#SYSTEM}.
     */
    @GuardedBy("mLock")
    private String mSwitchingFromSystemUserMessage;

    /**
     * Messages for for switching to {@link android.os.UserHandle#SYSTEM}.
     */
    @GuardedBy("mLock")
    private String mSwitchingToSystemUserMessage;

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

    private final LockPatternUtils mLockPatternUtils;

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

    @VisibleForTesting
    UserController(Injector injector) {
        mInjector = 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 finishUserSwitch(UserState uss) {
        finishUserBoot(uss);
        startProfiles();
        synchronized (mLock) {
            stopRunningUsersLU(mMaxRunningUsers);
        }
    }

    List<Integer> getRunningUsersLU() {
        ArrayList<Integer> runningUsers = new ArrayList<>();
        for (Integer userId : mUserLru) {
            UserState uss = mStartedUsers.get(userId);
            if (uss == null) {
                // Shouldn't happen, but be sane if it does.
                continue;
            }
            if (uss.state == UserState.STATE_STOPPING
                    || uss.state == UserState.STATE_SHUTDOWN) {
                // This user is already stopping, doesn't count.
                continue;
            }
            if (userId == UserHandle.USER_SYSTEM) {
                // We only count system user as running when it is not a pure system user.
                if (UserInfo.isSystemOnly(userId)) {
                    continue;
                }
            }
            runningUsers.add(userId);
        }
        return runningUsers;
    }

    void stopRunningUsersLU(int maxRunningUsers) {
        List<Integer> currentlyRunning = getRunningUsersLU();
        Iterator<Integer> iterator = currentlyRunning.iterator();
        while (currentlyRunning.size() > maxRunningUsers && iterator.hasNext()) {
            Integer userId = iterator.next();
            if (userId == UserHandle.USER_SYSTEM || userId == mCurrentUserId) {
                // Owner/System user and current user can't be stopped
                continue;
            }
            if (stopUsersLU(userId, false, null) == USER_OP_SUCCESS) {
                iterator.remove();
            }
        }
    }

    /**
     * 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();

        Slog.d(TAG, "Finishing 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.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()) {
                int uptimeSeconds = (int)(SystemClock.elapsedRealtime() / 1000);
                MetricsLogger.histogram(mInjector.getContext(),
                        "framework_locked_boot_completed", uptimeSeconds);
                final int MAX_UPTIME_SECONDS = 120;
                if (uptimeSeconds > MAX_UPTIME_SECONDS) {
                    Slog.wtf("SystemServerTiming",
                            "finishUserBoot took too long. uptimeSeconds=" + uptimeSeconds);
                }
            }

            mHandler.sendMessage(mHandler.obtainMessage(REPORT_LOCKED_BOOT_COMPLETE_MSG,
                    userId, 0));
            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_INCLUDE_BACKGROUND);
            mInjector.broadcastIntent(intent, null, resultTo, 0, null, null,
                    new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
                    AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
        }

        // We need to delay unlocking managed profiles until the parent user
        // is also unlocked.
        if (mInjector.getUserManager().isManagedProfile(userId)) {
            final UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
            if (parent != null
                    && isUserRunning(parent.id, ActivityManager.FLAG_AND_UNLOCKED)) {
                Slog.d(TAG, "User " + userId + " (parent " + parent.id
                        + "): attempting unlock because parent is unlocked");
                maybeUnlockUser(userId);
            } else {
                String parentId = (parent == null) ? "<null>" : String.valueOf(parent.id);
                Slog.d(TAG, "User " + userId + " (parent " + parentId
                        + "): delaying unlock because parent is locked");
            }
        } else {
            maybeUnlockUser(userId);
        }
    }

    /**
     * Step from {@link UserState#STATE_RUNNING_LOCKED} to
     * {@link UserState#STATE_RUNNING_UNLOCKING}.
     */
    private void finishUserUnlocking(final UserState uss) {
        final int userId = uss.mHandle.getIdentifier();
        // Only keep marching forward if user is actually unlocked
        if (!StorageManager.isUserKeyUnlocked(userId)) return;
        synchronized (mLock) {
            // Do not proceed if unexpected state or a stale user
            if (mStartedUsers.get(userId) != uss || uss.state != STATE_RUNNING_LOCKED) {
                return;
            }
        }
        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(() -> {
            mInjector.getUserManager().onBeforeUnlockUser(userId);
            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);

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

    /**
     * Step from {@link UserState#STATE_RUNNING_UNLOCKING} to
     * {@link UserState#STATE_RUNNING_UNLOCKED}.
     */
    void finishUserUnlocked(final UserState uss) {
        final int userId = uss.mHandle.getIdentifier();
        // Only keep marching forward if user is actually unlocked
        if (!StorageManager.isUserKeyUnlocked(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();
        // 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, false, MY_PID, SYSTEM_UID,
                userId);

        if (getUserInfo(userId).isManagedProfile()) {
            UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
            if (parent != null) {
                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, false, MY_PID, SYSTEM_UID,
                        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, Build.FINGERPRINT)) {
            // Suppress double notifications for managed profiles that
            // were unlocked automatically as part of their parent user
            // being unlocked.
            final boolean quiet;
            if (info.isManagedProfile()) {
                quiet = !uss.tokenProvided
                        || !mLockPatternUtils.isSeparateProfileChallengeEnabled(userId);
            } else {
                quiet = false;
            }
            mInjector.sendPreBootBroadcast(userId, quiet,
                    () -> finishUserUnlockedCompleted(uss));
        } else {
            finishUserUnlockedCompleted(uss);
        }
    }

    private void finishUserUnlockedCompleted(UserState uss) {
        final int userId = uss.mHandle.getIdentifier();
        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 user is actually unlocked
        if (!StorageManager.isUserKeyUnlocked(userId)) return;

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

        if (!userInfo.isInitialized()) {
            if (userId != UserHandle.USER_SYSTEM) {
                Slog.d(TAG, "Initializing user #" + userId);
                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
                                mInjector.getUserManager().makeInitialized(userInfo.id);
                            }
                        }, 0, null, null, null, AppOpsManager.OP_NONE,
                        null, true, false, MY_PID, SYSTEM_UID, userId);
            }
        }

        Slog.i(TAG, "Sending BOOT_COMPLETE user #" + userId);
        // Do not report secondary users, runtime restarts or first boot/upgrade
        if (userId == UserHandle.USER_SYSTEM
                && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
            int uptimeSeconds = (int) (SystemClock.elapsedRealtime() / 1000);
            MetricsLogger.histogram(mInjector.getContext(), "framework_boot_completed",
                    uptimeSeconds);
        }
        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);
        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 {
                        Slog.i(UserController.TAG, "Finished processing BOOT_COMPLETED for u" + userId);
                    }
                }, 0, null, null,
                new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
                AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
    }

    int restartUser(final int userId, final boolean foreground) {
        return stopUser(userId, /* force */ true, new IStopUserCallback.Stub() {
            @Override
            public void userStopped(final int userId) {
                // Post to the same handler that this callback is called from to ensure the user
                // cleanup is complete before restarting.
                mHandler.post(() -> startUser(userId, foreground));
            }
            @Override
            public void userStopAborted(final int userId) {}
        });
    }

    int stopUser(final int userId, final boolean force, final IStopUserCallback callback) {
        if (mInjector.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
                != PackageManager.PERMISSION_GRANTED) {
            String msg = "Permission Denial: switchUser() from pid="
                    + Binder.getCallingPid()
                    + ", uid=" + Binder.getCallingUid()
                    + " requires " + INTERACT_ACROSS_USERS_FULL;
            Slog.w(TAG, msg);
            throw new SecurityException(msg);
        }
        if (userId < 0 || userId == UserHandle.USER_SYSTEM) {
            throw new IllegalArgumentException("Can't stop system user " + userId);
        }
        enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
        synchronized (mLock) {
            return stopUsersLU(userId, force, callback);
        }
    }

    /**
     * Stops the user along with its related users. The method calls
     * {@link #getUsersToStopLU(int)} to determine the list of users that should be stopped.
     */
    private int stopUsersLU(final int userId, boolean force, final IStopUserCallback callback) {
        if (userId == UserHandle.USER_SYSTEM) {
            return USER_OP_ERROR_IS_SYSTEM;
        }
        if (isCurrentUserLU(userId)) {
            return USER_OP_IS_CURRENT;
        }
        int[] usersToStop = getUsersToStopLU(userId);
        // If one of related users is system or current, no related users should be stopped
        for (int i = 0; i < usersToStop.length; i++) {
            int relatedUserId = usersToStop[i];
            if ((UserHandle.USER_SYSTEM == relatedUserId) || isCurrentUserLU(relatedUserId)) {
                if (DEBUG_MU) Slog.i(TAG, "stopUsersLocked cannot stop related user "
                        + relatedUserId);
                // We still need to stop the requested user if it's a force stop.
                if (force) {
                    Slog.i(TAG,
                            "Force stop user " + userId + ". Related users will not be stopped");
                    stopSingleUserLU(userId, callback);
                    return USER_OP_SUCCESS;
                }
                return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
            }
        }
        if (DEBUG_MU) Slog.i(TAG, "stopUsersLocked usersToStop=" + Arrays.toString(usersToStop));
        for (int userIdToStop : usersToStop) {
            stopSingleUserLU(userIdToStop, userIdToStop == userId ? callback : null);
        }
        return USER_OP_SUCCESS;
    }

    private void stopSingleUserLU(final int userId, final IStopUserCallback callback) {
        if (DEBUG_MU) Slog.i(TAG, "stopSingleUserLocked userId=" + userId);
        final UserState uss = mStartedUsers.get(userId);
        if (uss == null) {
            // User is not started, nothing to do...  but we do need to
            // callback if requested.
            if (callback != null) {
                mHandler.post(() -> {
                    try {
                        callback.userStopped(userId);
                    } catch (RemoteException e) {
                    }
                });
            }
            return;
        }

        if (callback != null) {
            uss.mStopCallbacks.add(callback);
        }

        if (uss.state != UserState.STATE_STOPPING
                && uss.state != UserState.STATE_SHUTDOWN) {
            uss.setState(UserState.STATE_STOPPING);
            mInjector.getUserManagerInternal().setUserState(userId, uss.state);
            updateStartedUserArrayLU();

            // 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) {
                        mHandler.post(() -> finishUserStopping(userId, uss));
                    }
                };

                // Clear broadcast queue for the user to avoid delivering stale broadcasts
                mInjector.clearBroadcastQueueForUser(userId);
                // Kick things off.
                mInjector.broadcastIntent(stoppingIntent,
                        null, stoppingReceiver, 0, null, null,
                        new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
                        null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
            });
        }
    }

    void finishUserStopping(final int userId, final UserState uss) {
        // On to the next.
        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) {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        finishUserStopped(uss);
                    }
                });
            }
        };

        synchronized (mLock) {
            if (uss.state != UserState.STATE_STOPPING) {
                // Whoops, we are being started back up.  Abort, abort!
                return;
            }
            uss.setState(UserState.STATE_SHUTDOWN);
        }
        mInjector.getUserManagerInternal().setUserState(userId, uss.state);

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

        mInjector.broadcastIntent(shutdownIntent,
                null, shutdownReceiver, 0, null, null, null,
                AppOpsManager.OP_NONE,
                null, true, false, MY_PID, SYSTEM_UID, userId);
    }

    void finishUserStopped(UserState uss) {
        final int userId = uss.mHandle.getIdentifier();
        boolean stopped;
        ArrayList<IStopUserCallback> callbacks;
        boolean forceStopUser = false;
        synchronized (mLock) {
            callbacks = new ArrayList<>(uss.mStopCallbacks);
            if (mStartedUsers.get(userId) != uss) {
                stopped = false;
            } else if (uss.state != UserState.STATE_SHUTDOWN) {
                stopped = false;
            } else {
                stopped = true;
                // User can no longer run.
                mStartedUsers.remove(userId);
                mUserLru.remove(Integer.valueOf(userId));
                updateStartedUserArrayLU();
                forceStopUser = true;
            }
        }
        if (forceStopUser) {
            mInjector.getUserManagerInternal().removeUserState(userId);
            mInjector.activityManagerOnUserStopped(userId);
            // Clean up all state and processes associated with the user.
            // Kill all the processes for the user.
            forceStopUser(userId, "finish user");
        }

        for (int i = 0; i < callbacks.size(); i++) {
            try {
                if (stopped) callbacks.get(i).userStopped(userId);
                else callbacks.get(i).userStopAborted(userId);
            } catch (RemoteException e) {
            }
        }

        if (stopped) {
            mInjector.systemServiceManagerCleanupUser(userId);
            mInjector.stackSupervisorRemoveUser(userId);
            // Remove the user if it is ephemeral.
            if (getUserInfo(userId).isEphemeral()) {
                mInjector.getUserManager().removeUserEvenWhenDisallowed(userId);
            }
            // Evict the user's credential encryption key.
            try {
                getStorageManager().lockUserKey(userId);
            } catch (RemoteException re) {
                throw re.rethrowAsRuntimeException();
            }
        }
    }

    /**
     * Determines the list of users that should be stopped together with the specified
     * {@code userId}. The returned list includes {@code userId}.
     */
    private @NonNull int[] getUsersToStopLU(int userId) {
        int startedUsersSize = mStartedUsers.size();
        IntArray userIds = new IntArray();
        userIds.add(userId);
        int userGroupId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
        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);
        }
        return userIds.toArray();
    }

    private void forceStopUser(int userId, String reason) {
        mInjector.activityManagerForceStopPackage(userId, reason);
        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, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
    }

    /**
     * Stops the guest or ephemeral user if it has gone to the background.
     */
    private void stopGuestOrEphemeralUserIfBackground(int oldUserId) {
        if (DEBUG_MU) Slog.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.
            synchronized (mLock) {
                stopUsersLU(oldUserId, true, null);
            }
        }
    }

    void scheduleStartProfiles() {
        if (!mHandler.hasMessages(START_PROFILES_MSG)) {
            mHandler.sendMessageDelayed(mHandler.obtainMessage(START_PROFILES_MSG),
                    DateUtils.SECOND_IN_MILLIS);
        }
    }

    void startProfiles() {
        int currentUserId = getCurrentUserId();
        if (DEBUG_MU) Slog.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 && !user.isQuietModeEnabled()) {
                profilesToStart.add(user);
            }
        }
        final int profilesToStartSize = profilesToStart.size();
        int i = 0;
        for (; i < profilesToStartSize && i < (mMaxRunningUsers - 1); ++i) {
            startUser(profilesToStart.get(i).id, /* foreground= */ false);
        }
        if (i < profilesToStartSize) {
            Slog.w(TAG, "More profiles than MAX_RUNNING_USERS");
        }
    }

    private IStorageManager getStorageManager() {
        return IStorageManager.Stub.asInterface(ServiceManager.getService("mount"));
    }
    boolean startUser(final int userId, final boolean foreground) {
        return startUser(userId, foreground, null);
    }

    /**
     * Start user, if its not already running.
     * <p>The user will be brought to the foreground, if {@code foreground} parameter is set.
     * 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 true
     *     <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 true
     *     <li>{@link Intent#ACTION_USER_SWITCHED} - sent to registered receivers of the new user.
     *     Sent only if {@code foreground} parameter is 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 foreground true if user should be brought to the foreground
     * @return true if the user has been successfully started
     */
    boolean startUser(
            final int userId,
            final boolean foreground,
            @Nullable IProgressListener unlockListener) {
        if (mInjector.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
                != PackageManager.PERMISSION_GRANTED) {
            String msg = "Permission Denial: switchUser() from pid="
                    + Binder.getCallingPid()
                    + ", uid=" + Binder.getCallingUid()
                    + " requires " + INTERACT_ACROSS_USERS_FULL;
            Slog.w(TAG, msg);
            throw new SecurityException(msg);
        }

        Slog.i(TAG, "Starting userid:" + userId + " fg:" + foreground);

        final long ident = Binder.clearCallingIdentity();
        try {
            final int oldUserId = getCurrentUserId();
            if (oldUserId == userId) {
                return true;
            }

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

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

            if (foreground && mUserSwitchUiEnabled) {
                mInjector.getWindowManager().startFreezingScreen(
                        R.anim.screen_user_exit, R.anim.screen_user_enter);
            }

            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.
            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;
                }
                final Integer userIdInt = userId;
                mUserLru.remove(userIdInt);
                mUserLru.add(userIdInt);
            }
            if (unlockListener != null) {
                uss.mUnlockProgress.addListener(unlockListener);
            }
            if (updateUmState) {
                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
            }
            if (foreground) {
                synchronized (mLock) {
                    mCurrentUserId = userId;
                    mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
                }
                mInjector.updateUserConfiguration();
                updateCurrentProfileIds();
                mInjector.getWindowManager().setCurrentUser(userId, getCurrentProfileIds());
                // 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 (mUserSwitchUiEnabled) {
                    mInjector.getWindowManager().setSwitchingUser(true);
                    mInjector.getWindowManager().lockNow(null);
                }
            } else {
                final Integer currentUserIdInt = mCurrentUserId;
                updateCurrentProfileIds();
                mInjector.getWindowManager().setCurrentProfileIds(getCurrentProfileIds());
                synchronized (mLock) {
                    mUserLru.remove(currentUserIdInt);
                    mUserLru.add(currentUserIdInt);
                }
            }

            // 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) {
                // 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;
            } else if (uss.state == UserState.STATE_SHUTDOWN) {
                // 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;
            }

            if (uss.state == UserState.STATE_BOOTING) {
                // 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(SYSTEM_USER_START_MSG, userId, 0));
            }

            if (foreground) {
                mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
                        oldUserId));
                mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
                mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
                mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
                        oldUserId, userId, uss));
                mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
                        oldUserId, userId, uss), USER_SWITCH_TIMEOUT_MS);
            }

            if (needStart) {
                // Send USER_STARTED broadcast
                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,
                        null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                        null, false, false, MY_PID, SYSTEM_UID, userId);
            }

            if (foreground) {
                moveUserToForeground(uss, oldUserId, userId);
            } else {
                finishUserBoot(uss);
            }

            if (needStart) {
                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,
                        null, new IIntentReceiver.Stub() {
                            @Override
                            public void performReceive(Intent intent, int resultCode,
                                    String data, Bundle extras, boolean ordered,
                                    boolean sticky,
                                    int sendingUser) throws RemoteException {
                            }
                        }, 0, null, null,
                        new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
                        null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }

        return true;
    }

    /**
     * Start user, if its not already running, and bring it to foreground.
     */
    void startUserInForeground(final int targetUserId) {
        boolean success = startUser(targetUserId, /* foreground */ true);
        if (!success) {
            mInjector.getWindowManager().setSwitchingUser(false);
        }
    }

    boolean unlockUser(final int userId, byte[] token, byte[] secret, IProgressListener listener) {
        if (mInjector.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
                != PackageManager.PERMISSION_GRANTED) {
            String msg = "Permission Denial: unlockUser() from pid="
                    + Binder.getCallingPid()
                    + ", uid=" + Binder.getCallingUid()
                    + " requires " + INTERACT_ACROSS_USERS_FULL;
            Slog.w(TAG, msg);
            throw new SecurityException(msg);
        }

        final long binderToken = Binder.clearCallingIdentity();
        try {
            return unlockUserCleared(userId, token, secret, listener);
        } finally {
            Binder.restoreCallingIdentity(binderToken);
        }
    }

    /**
     * Attempt to unlock user without a credential token. This typically
     * succeeds when the device doesn't have credential-encrypted storage, or
     * when the the credential-encrypted storage isn't tied to a user-provided
     * PIN or pattern.
     */
    private boolean maybeUnlockUser(final int userId) {
        // Try unlocking storage using empty token
        return unlockUserCleared(userId, null, null, null);
    }

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

    private boolean unlockUserCleared(final int userId, byte[] token, byte[] secret,
            IProgressListener listener) {
        UserState uss;
        if (!StorageManager.isUserKeyUnlocked(userId)) {
            final UserInfo userInfo = getUserInfo(userId);
            final IStorageManager storageManager = getStorageManager();
            try {
                // We always want to unlock user storage, even user is not started yet
                storageManager.unlockUserKey(userId, userInfo.serialNumber, token, secret);
            } catch (RemoteException | RuntimeException e) {
                Slog.w(TAG, "Failed to unlock: " + e.getMessage());
            }
        }
        synchronized (mLock) {
            // Register the given listener to watch for unlock progress
            uss = mStartedUsers.get(userId);
            if (uss != null) {
                uss.mUnlockProgress.addListener(listener);
                uss.tokenProvided = (token != null);
            }
        }
        // Bail if user isn't actually running
        if (uss == null) {
            notifyFinished(userId, listener);
            return false;
        }

        finishUserUnlocking(uss);

        // We just unlocked a user, so let's now attempt to unlock any
        // managed 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) {
                Slog.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);
        int currentUserId = getCurrentUserId();
        UserInfo targetUserInfo = getUserInfo(targetUserId);
        if (targetUserId == currentUserId) {
            Slog.i(TAG, "user #" + targetUserId + " is already the current user");
            return true;
        }
        if (targetUserInfo == null) {
            Slog.w(TAG, "No user info for user #" + targetUserId);
            return false;
        }
        if (!targetUserInfo.isDemo() && UserManager.isDeviceInDemoMode(mInjector.getContext())) {
            Slog.w(TAG, "Cannot switch to non-demo user #" + targetUserId
                    + " when device is in demo mode");
            return false;
        }
        if (!targetUserInfo.supportsSwitchTo()) {
            Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not supported");
            return false;
        }
        if (targetUserInfo.isManagedProfile()) {
            Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not a full user");
            return false;
        }
        synchronized (mLock) {
            mTargetUserId = targetUserId;
        }
        if (mUserSwitchUiEnabled) {
            UserInfo currentUserInfo = getUserInfo(currentUserId);
            Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
            mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
            mUiHandler.sendMessage(mHandler.obtainMessage(
                    START_USER_SWITCH_UI_MSG, userNames));
        } else {
            mHandler.removeMessages(START_USER_SWITCH_FG_MSG);
            mHandler.sendMessage(mHandler.obtainMessage(
                    START_USER_SWITCH_FG_MSG, targetUserId, 0));
        }
        return true;
    }

    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,
                getSwitchingFromSystemUserMessage(), getSwitchingToSystemUserMessage());
    }

    private void dispatchForegroundProfileChanged(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();
    }

    /** Called on handler thread */
    void dispatchUserSwitchComplete(int userId) {
        mInjector.getWindowManager().setSwitchingUser(false);
        final int observerCount = mUserSwitchObservers.beginBroadcast();
        for (int i = 0; i < observerCount; i++) {
            try {
                mUserSwitchObservers.getBroadcastItem(i).onUserSwitchComplete(userId);
            } catch (RemoteException e) {
            }
        }
        mUserSwitchObservers.finishBroadcast();
    }

    private void dispatchLockedBootComplete(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();
    }

    private void stopBackgroundUsersIfEnforced(int oldUserId) {
        // Never stop system user
        if (oldUserId == UserHandle.USER_SYSTEM) {
            return;
        }
        // For now, only check for user restriction. Additional checks can be added here
        boolean disallowRunInBg = hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND,
                oldUserId);
        if (!disallowRunInBg) {
            return;
        }
        synchronized (mLock) {
            if (DEBUG_MU) Slog.i(TAG, "stopBackgroundUsersIfEnforced stopping " + oldUserId
                    + " and related users");
            stopUsersLU(oldUserId, false, null);
        }
    }

    private void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
        synchronized (mLock) {
            Slog.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);
        }
    }

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

    void dispatchUserSwitch(final UserState uss, final int oldUserId, final int newUserId) {
        Slog.d(TAG, "Dispatch onUserSwitching oldUser #" + oldUserId + " newUser #" + newUserId);
        final int observerCount = mUserSwitchObservers.beginBroadcast();
        if (observerCount > 0) {
            final ArraySet<String> curWaitingUserSwitchCallbacks = new ArraySet<>();
            synchronized (mLock) {
                uss.switching = true;
                mCurWaitingUserSwitchCallbacks = curWaitingUserSwitchCallbacks;
            }
            final AtomicInteger waitingCallbacksCount = new AtomicInteger(observerCount);
            final long dispatchStartedTime = SystemClock.elapsedRealtime();
            for (int i = 0; i < observerCount; i++) {
                try {
                    // 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 {
                            synchronized (mLock) {
                                long delay = SystemClock.elapsedRealtime() - dispatchStartedTime;
                                if (delay > USER_SWITCH_TIMEOUT_MS) {
                                    Slog.e(TAG, "User switch timeout: observer "  + name
                                            + " sent result after " + delay + " ms");
                                }
                                curWaitingUserSwitchCallbacks.remove(name);
                                // Continue switching if all callbacks have been notified and
                                // user switching session is still valid
                                if (waitingCallbacksCount.decrementAndGet() == 0
                                        && (curWaitingUserSwitchCallbacks
                                        == mCurWaitingUserSwitchCallbacks)) {
                                    sendContinueUserSwitchLU(uss, oldUserId, newUserId);
                                }
                            }
                        }
                    };
                    mUserSwitchObservers.getBroadcastItem(i).onUserSwitching(newUserId, callback);
                } catch (RemoteException e) {
                }
            }
        } else {
            synchronized (mLock) {
                sendContinueUserSwitchLU(uss, oldUserId, newUserId);
            }
        }
        mUserSwitchObservers.finishBroadcast();
    }

    void sendContinueUserSwitchLU(UserState uss, int oldUserId, int newUserId) {
        mCurWaitingUserSwitchCallbacks = null;
        mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
        mHandler.sendMessage(mHandler.obtainMessage(CONTINUE_USER_SWITCH_MSG,
                oldUserId, newUserId, uss));
    }

    void continueUserSwitch(UserState uss, int oldUserId, int newUserId) {
        Slog.d(TAG, "Continue user switch oldUser #" + oldUserId + ", newUser #" + newUserId);
        if (mUserSwitchUiEnabled) {
            mInjector.getWindowManager().stopFreezingScreen();
        }
        uss.switching = false;
        mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
        mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG,
                newUserId, 0));
        stopGuestOrEphemeralUserIfBackground(oldUserId);
        stopBackgroundUsersIfEnforced(oldUserId);
    }

    private void moveUserToForeground(UserState uss, int oldUserId, int newUserId) {
        boolean homeInFront = mInjector.stackSupervisorSwitchUser(newUserId, uss);
        if (homeInFront) {
            mInjector.startHomeActivity(newUserId, "moveUserToForeground");
        } else {
            mInjector.stackSupervisorResumeFocusedStackTopActivity();
        }
        EventLogTags.writeAmSwitchUser(newUserId);
        sendUserSwitchBroadcasts(oldUserId, newUserId);
    }

    void sendUserSwitchBroadcasts(int oldUserId, int newUserId) {
        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);
                    mInjector.broadcastIntent(intent,
                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                            null, false, false, MY_PID, SYSTEM_UID, 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);
                    mInjector.broadcastIntent(intent,
                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                            null, false, false, MY_PID, SYSTEM_UID, 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);
                mInjector.broadcastIntent(intent,
                        null, null, 0, null, null,
                        new String[] {android.Manifest.permission.MANAGE_USERS},
                        AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
                        UserHandle.USER_ALL);
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }


    int handleIncomingUser(int callingPid, int callingUid, 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.
        int targetUserId = unsafeConvertIncomingUser(userId);

        if (callingUid != 0 && callingUid != SYSTEM_UID) {
            final boolean allow;
            if (mInjector.isCallerRecents(callingUid)
                    && callingUserId == getCurrentUserId()
                    && isSameProfileGroup(callingUserId, targetUserId)) {
                // If the caller is Recents and it is running in the current user, 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 (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) {
                // 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(callingUserId, targetUserId);
            } 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 user ");
                    builder.append(UserHandle.getUserId(callingUid));
                    builder.append("; this requires ");
                    builder.append(INTERACT_ACROSS_USERS_FULL);
                    if (allowMode != ALLOW_FULL_ONLY) {
                        builder.append(" or ");
                        builder.append(INTERACT_ACROSS_USERS);
                    }
                    String msg = builder.toString();
                    Slog.w(TAG, msg);
                    throw new SecurityException(msg);
                }
            }
        }
        if (!allowAll && targetUserId < 0) {
            throw new IllegalArgumentException(
                    "Call does not support special user #" + 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;
    }

    int unsafeConvertIncomingUser(int userId) {
        return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
                ? getCurrentUserId(): userId;
    }

    void registerUserSwitchObserver(IUserSwitchObserver observer, String name) {
        Preconditions.checkNotNull(name, "Observer name cannot be null");
        if (mInjector.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
                != PackageManager.PERMISSION_GRANTED) {
            final String msg = "Permission Denial: registerUserSwitchObserver() from pid="
                    + Binder.getCallingPid()
                    + ", uid=" + Binder.getCallingUid()
                    + " requires " + INTERACT_ACROSS_USERS_FULL;
            Slog.w(TAG, msg);
            throw new SecurityException(msg);
        }
        mUserSwitchObservers.register(observer, name);
    }

    void sendForegroundProfileChanged(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(int userId) {
        synchronized (mLock) {
            return mStartedUsers.get(userId);
        }
    }

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

    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);
            }
        }
    }

    void sendBootCompleted(IIntentReceiver resultTo) {
        // Get a copy of mStartedUsers to use outside of lock
        SparseArray<UserState> startedUsers;
        synchronized (mLock) {
            startedUsers = mStartedUsers.clone();
        }
        for (int i = 0; i < startedUsers.size(); i++) {
            UserState uss = startedUsers.valueAt(i);
            finishUserBoot(uss, resultTo);
        }
    }

    void onSystemReady() {
        updateCurrentProfileIds();
    }

    /**
     * Refreshes the list of users related to the current user when either a
     * user switch happens or when a new related user is started in the
     * background.
     */
    private void updateCurrentProfileIds() {
        final List<UserInfo> profiles = mInjector.getUserManager().getProfiles(getCurrentUserId(),
                false /* enabledOnly */);
        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(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 key
                case UserState.STATE_STOPPING:
                case UserState.STATE_SHUTDOWN:
                    return StorageManager.isUserKeyUnlocked(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 key
                case UserState.STATE_STOPPING:
                case UserState.STATE_SHUTDOWN:
                    return StorageManager.isUserKeyUnlocked(userId);
                default:
                    return false;
            }
        }

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

    UserInfo getCurrentUser() {
        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;
            Slog.w(TAG, msg);
            throw new SecurityException(msg);
        }

        // Optimization - if there is no pending user switch, return current id
        if (mTargetUserId == UserHandle.USER_NULL) {
            return getUserInfo(mCurrentUserId);
        }
        synchronized (mLock) {
            return getCurrentUserLU();
        }
    }

    UserInfo getCurrentUserLU() {
        int userId = mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
        return getUserInfo(userId);
    }

    int getCurrentOrTargetUserId() {
        synchronized (mLock) {
            return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
        }
    }

    int getCurrentOrTargetUserIdLU() {
        return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
    }


    int getCurrentUserIdLU() {
        return mCurrentUserId;
    }

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

    private boolean isCurrentUserLU(int userId) {
        return userId == getCurrentOrTargetUserIdLU();
    }

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

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

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

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

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

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

    Set<Integer> getProfileIds(int userId) {
        Set<Integer> userIds = new HashSet<>();
        final List<UserInfo> profiles = mInjector.getUserManager().getProfiles(userId,
                false /* enabledOnly */);
        for (UserInfo user : profiles) {
            userIds.add(user.id);
        }
        return userIds;
    }

    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(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(int userId) {
        synchronized (mLock) {
            return ArrayUtils.contains(mCurrentProfileIds, userId);
        }
    }

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

    void onUserRemoved(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);
        }
    }

    /**
     * Returns whether the given user requires credential entry at this time. This is used to
     * intercept activity launches for work apps when the Work Challenge is present.
     */
    protected boolean shouldConfirmCredentials(int userId) {
        synchronized (mLock) {
            if (mStartedUsers.get(userId) == null) {
                return false;
            }
        }
        if (!mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
            return false;
        }
        final KeyguardManager km = mInjector.getKeyguardManager();
        return km.isDeviceLocked(userId) && km.isDeviceSecure(userId);
    }

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

    void setSwitchingFromSystemUserMessage(String switchingFromSystemUserMessage) {
        synchronized (mLock) {
            mSwitchingFromSystemUserMessage = switchingFromSystemUserMessage;
        }
    }

    void setSwitchingToSystemUserMessage(String switchingToSystemUserMessage) {
        synchronized (mLock) {
            mSwitchingToSystemUserMessage = switchingToSystemUserMessage;
        }
    }

    private String getSwitchingFromSystemUserMessage() {
        synchronized (mLock) {
            return mSwitchingFromSystemUserMessage;
        }
    }

    private String getSwitchingToSystemUserMessage() {
        synchronized (mLock) {
            return mSwitchingToSystemUserMessage;
        }
    }

    void writeToProto(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.writeToProto(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.end(token);
        }
    }

    void dump(PrintWriter pw, boolean dumpAll) {
        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));
                }
            }
        }
    }

    public boolean handleMessage(Message msg) {
        switch (msg.what) {
            case START_USER_SWITCH_FG_MSG:
                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 SYSTEM_USER_START_MSG:
                mInjector.batteryStatsServiceNoteEvent(
                        BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
                        Integer.toString(msg.arg1), msg.arg1);
                mInjector.getSystemServiceManager().startUser(msg.arg1);
                break;
            case SYSTEM_USER_UNLOCK_MSG:
                final int userId = msg.arg1;
                mInjector.getSystemServiceManager().unlockUser(userId);
                // Loads recents on a worker thread that allows disk I/O
                FgThread.getHandler().post(() -> {
                    mInjector.loadUserRecents(userId);
                });
                if (userId == UserHandle.USER_SYSTEM) {
                    mInjector.startPersistentApps(PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
                }
                mInjector.installEncryptionUnawareProviders(userId);
                finishUserUnlocked((UserState) msg.obj);
                break;
            case SYSTEM_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().switchUser(msg.arg1);
                break;
            case FOREGROUND_PROFILE_CHANGED_MSG:
                dispatchForegroundProfileChanged(msg.arg1);
                break;
            case REPORT_USER_SWITCH_COMPLETE_MSG:
                dispatchUserSwitchComplete(msg.arg1);
                break;
            case REPORT_LOCKED_BOOT_COMPLETE_MSG:
                dispatchLockedBootComplete(msg.arg1);
                break;
            case START_USER_SWITCH_UI_MSG:
                showUserSwitchDialog((Pair<UserInfo, UserInfo>) msg.obj);
                break;
        }
        return false;
    }

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

        @Override
        public void onProgress(int id, int progress, Bundle extras) throws RemoteException {
            Slog.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 TimingsTraceLog("SystemServerTiming", Trace.TRACE_TAG_SYSTEM_SERVER)
                        .logDuration("SystemUserUnlock", unlockTime);
            } else {
                Slog.d(TAG, "Unlocking user " + id + " took " + unlockTime + " ms");
            }
        }
    };

    @VisibleForTesting
    static class Injector {
        private final ActivityManagerService mService;
        private UserManagerService mUserManager;
        private UserManagerInternal mUserManagerInternal;

        Injector(ActivityManagerService service) {
            mService = service;
        }

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

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

        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 ordered, boolean sticky, int callingPid, int callingUid, int userId) {
            // TODO b/64165549 Verify that mLock is not held before calling AMS methods
            synchronized (mService) {
                return mService.broadcastIntentLocked(null, null, intent, resolvedType, resultTo,
                        resultCode, resultData, resultExtras, requiredPermissions, appOp, bOptions,
                        ordered, sticky, callingPid, callingUid, userId);
            }
        }

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

        WindowManagerService getWindowManager() {
            return mService.mWindowManager;
        }
        void activityManagerOnUserStopped(int userId) {
            synchronized (mService) {
                mService.onUserStoppedLocked(userId);
            }
        }

        void systemServiceManagerCleanupUser(int userId) {
            mService.mSystemServiceManager.cleanupUser(userId);
        }

        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;
        }

        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 mService.mSystemServiceManager.isRuntimeRestarted();
        }

        SystemServiceManager getSystemServiceManager() {
            return mService.mSystemServiceManager;
        }

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

        void sendPreBootBroadcast(int userId, boolean quiet, final Runnable onFinish) {
            new PreBootBroadcaster(mService, userId, null, quiet) {
                @Override
                public void onFinished() {
                    onFinish.run();
                }
            }.sendNext();
        }

        void activityManagerForceStopPackage(int userId, String reason) {
            synchronized (mService) {
                mService.forceStopPackageLocked(null, -1, false, false, true, false, false,
                        userId, reason);
            }
        };

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

        protected void startHomeActivity(int userId, String reason) {
            synchronized (mService) {
                mService.startHomeActivityLocked(userId, reason);
            }
        }

        void updateUserConfiguration() {
            synchronized (mService) {
                mService.updateUserConfigurationLocked();
            }
        }

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

        void loadUserRecents(int userId) {
            synchronized (mService) {
                mService.getRecentTasks().loadUserRecentsLocked(userId);
            }
        }

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

        void installEncryptionUnawareProviders(int userId) {
            mService.installEncryptionUnawareProviders(userId);
        }

        void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser,
                String switchingFromSystemUserMessage, String switchingToSystemUserMessage) {
            Dialog d = new UserSwitchingDialog(mService, mService.mContext, fromUser, toUser,
                    true /* above system */, switchingFromSystemUserMessage,
                    switchingToSystemUserMessage);
            d.show();
        }

        void stackSupervisorRemoveUser(int userId) {
            synchronized (mService) {
                mService.mStackSupervisor.removeUserLocked(userId);
            }
        }

        protected boolean stackSupervisorSwitchUser(int userId, UserState uss) {
            synchronized (mService) {
                return mService.mStackSupervisor.switchUserLocked(userId, uss);
            }
        }

        protected void stackSupervisorResumeFocusedStackTopActivity() {
            synchronized (mService) {
                mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();
            }
        }

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

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