Add IActivityManager.stopUserWithDelayedLocking

- Only allow delayed locking for the explicit call, stopUserWithDelayedLocking()
  or for implicit user stopping caused by user switching.
- All other explicit user stopping should immediately lock the user. This allows
  immediate locking from component like DevicePolicy.
- Product still should enable delayed locking explicitly and if it is disabled,
  IActivityManager.stopUserWithDelayedLocking will behave the same with
  IActivityManager.stopUser.
- Once user is stopped in delayed locking mode, one of following steps can completely
  lock the user (this is not a complete list):
  1. User is asked to be stopped with IActivityManager.stopUser call.
  2. User is restarted through IActivityManager.restartUserInBackground call.
  3. UserManager.evictCredentialEncryptyionKey (involves restartUserInBackground call)
  4. When user is removed (involves IActivityManager.stopUser call).

Bug: 131757355
Test: run unit tests
      check user locking in car device where delayed locking is enabled.

Change-Id: I663ffde3a5b74738a6f59b4282ff562146f22662
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index e8494c4..7e981d2 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -345,6 +345,12 @@
             in Bundle options, int userId);
     @UnsupportedAppUsage
     int stopUser(int userid, boolean force, in IStopUserCallback callback);
+    /**
+     * Check {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int, boolean, IStopUserCallback)}
+     * for details.
+     */
+    int stopUserWithDelayedLocking(int userid, boolean force, in IStopUserCallback callback);
+
     @UnsupportedAppUsage
     void registerUserSwitchObserver(in IUserSwitchObserver observer, in String name);
     void unregisterUserSwitchObserver(in IUserSwitchObserver observer);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c21adb0..684da9e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -9108,13 +9108,14 @@
             final Resources res = mContext.getResources();
             mAppErrors.loadAppsNotReportingCrashesFromConfigLocked(res.getString(
                     com.android.internal.R.string.config_appsNotReportingCrashes));
-            mUserController.mUserSwitchUiEnabled = !res.getBoolean(
+            final boolean userSwitchUiEnabled = !res.getBoolean(
                     com.android.internal.R.bool.config_customUserSwitchUi);
-            mUserController.mMaxRunningUsers = res.getInteger(
+            final int maxRunningUsers = res.getInteger(
                     com.android.internal.R.integer.config_multiuserMaxRunningUsers);
-            mUserController.mDelayUserDataLocking = res.getBoolean(
+            final boolean delayUserDataLocking = res.getBoolean(
                     com.android.internal.R.bool.config_multiuserDelayUserDataLocking);
-
+            mUserController.setInitialConfig(userSwitchUiEnabled, maxRunningUsers,
+                    delayUserDataLocking);
             mWaitForNetworkTimeoutMs = waitForNetworkTimeoutMs;
             mPssDeferralTime = pssDeferralMs;
         }
@@ -17926,7 +17927,31 @@
 
     @Override
     public int stopUser(final int userId, boolean force, final IStopUserCallback callback) {
-        return mUserController.stopUser(userId, force, callback, null /* keyEvictedCallback */);
+        return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ false,
+                /* callback= */ callback, /* keyEvictedCallback= */ null);
+    }
+
+    /**
+     * Stops user but allow delayed locking. Delayed locking keeps user unlocked even after
+     * stopping only if {@code config_multiuserDelayUserDataLocking} overlay is set true.
+     *
+     * <p>When delayed locking is not enabled through the overlay, this call becomes the same
+     * with {@link #stopUser(int, boolean, IStopUserCallback)} call.
+     *
+     * @param userId User id to stop.
+     * @param force Force stop the user even if the user is related with system user or current
+     *              user.
+     * @param callback Callback called when user has stopped.
+     *
+     * @return {@link ActivityManager#USER_OP_SUCCESS} when user is stopped successfully. Returns
+     *         other {@code ActivityManager#USER_OP_*} codes for failure.
+     *
+     */
+    @Override
+    public int stopUserWithDelayedLocking(final int userId, boolean force,
+            final IStopUserCallback callback) {
+        return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ true,
+                /* callback= */ callback, /* keyEvictedCallback= */ null);
     }
 
     @Override
@@ -18332,7 +18357,7 @@
 
         @Override
         public int getMaxRunningUsers() {
-            return mUserController.mMaxRunningUsers;
+            return mUserController.getMaxRunningUsers();
         }
 
         @Override
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 8ae18ff6..f3a2e70 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -93,7 +93,6 @@
 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;
@@ -161,7 +160,8 @@
      * <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;
+    @GuardedBy("mLock")
+    private int mMaxRunningUsers;
 
     // Lock for internal state.
     private final Object mLock = new Object();
@@ -213,7 +213,8 @@
     private final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers
             = new RemoteCallbackList<>();
 
-    boolean mUserSwitchUiEnabled = true;
+    @GuardedBy("mLock")
+    private boolean mUserSwitchUiEnabled = true;
 
     /**
      * Currently active user switch callbacks.
@@ -246,10 +247,11 @@
     /**
      * In this mode, user is always stopped when switched out but locking of user data is
      * postponed until total number of unlocked users in the system reaches mMaxRunningUsers.
-     * Once total number of unlocked users reach mMaxRunningUsers, least recentely used user
+     * Once total number of unlocked users reach mMaxRunningUsers, least recently used user
      * will be locked.
      */
-    boolean mDelayUserDataLocking;
+    @GuardedBy("mLock")
+    private boolean mDelayUserDataLocking;
     /**
      * Keep track of last active users for mDelayUserDataLocking.
      * The latest stopped user is placed in front while the least recently stopped user in back.
@@ -275,6 +277,33 @@
         updateStartedUserArrayLU();
     }
 
+    void setInitialConfig(boolean userSwitchUiEnabled, int maxRunningUsers,
+            boolean delayUserDataLocking) {
+        synchronized (mLock) {
+            mUserSwitchUiEnabled = userSwitchUiEnabled;
+            mMaxRunningUsers = maxRunningUsers;
+            mDelayUserDataLocking = delayUserDataLocking;
+        }
+    }
+
+    private boolean isUserSwitchUiEnabled() {
+        synchronized (mLock) {
+            return mUserSwitchUiEnabled;
+        }
+    }
+
+    int getMaxRunningUsers() {
+        synchronized (mLock) {
+            return mMaxRunningUsers;
+        }
+    }
+
+    private boolean isDelayUserDataLockingEnabled() {
+        synchronized (mLock) {
+            return mDelayUserDataLocking;
+        }
+    }
+
     void finishUserSwitch(UserState uss) {
         // This call holds the AM lock so we post to the handler.
         mHandler.post(() -> {
@@ -321,7 +350,11 @@
                 // Owner/System user and current user can't be stopped
                 continue;
             }
-            if (stopUsersLU(userId, false, null, null) == USER_OP_SUCCESS) {
+            // allowDelayedLocking set here as stopping user is done without any explicit request
+            // from outside.
+            if (stopUsersLU(userId, /* force= */ false, /* allowDelayedLocking= */ true,
+                    /* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
+                    == USER_OP_SUCCESS) {
                 iterator.remove();
             }
         }
@@ -567,8 +600,8 @@
             // intialize it; it should be stopped right away as it's not really a "real" user.
             // TODO(b/143092698): in the long-term, it might be better to add a onCreateUser()
             // callback on SystemService instead.
-            stopUser(userInfo.id, /* force= */ true, /* stopUserCallback= */ null,
-                    /* keyEvictedCallback= */ null);
+            stopUser(userInfo.id, /* force= */ true, /* allowDelayedLocking= */ false,
+                    /* stopUserCallback= */ null, /* keyEvictedCallback= */ null);
             return;
         }
 
@@ -611,7 +644,8 @@
     }
 
     int restartUser(final int userId, final boolean foreground) {
-        return stopUser(userId, /* force */ true, null, new KeyEvictedCallback() {
+        return stopUser(userId, /* force= */ true, /* allowDelayedLocking= */ false,
+                /* stopUserCallback= */ null, new KeyEvictedCallback() {
             @Override
             public void keyEvicted(@UserIdInt int userId) {
                 // Post to the same handler that this callback is called from to ensure the user
@@ -621,15 +655,16 @@
         });
     }
 
-    int stopUser(final int userId, final boolean force, final IStopUserCallback stopUserCallback,
-            KeyEvictedCallback keyEvictedCallback) {
+    int stopUser(final int userId, final boolean force, boolean allowDelayedLocking,
+            final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
         checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "stopUser");
         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, stopUserCallback, keyEvictedCallback);
+            return stopUsersLU(userId, force, allowDelayedLocking, stopUserCallback,
+                    keyEvictedCallback);
         }
     }
 
@@ -638,7 +673,7 @@
      * {@link #getUsersToStopLU(int)} to determine the list of users that should be stopped.
      */
     @GuardedBy("mLock")
-    private int stopUsersLU(final int userId, boolean force,
+    private int stopUsersLU(final int userId, boolean force, boolean allowDelayedLocking,
             final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
         if (userId == UserHandle.USER_SYSTEM) {
             return USER_OP_ERROR_IS_SYSTEM;
@@ -657,7 +692,8 @@
                 if (force) {
                     Slog.i(TAG,
                             "Force stop user " + userId + ". Related users will not be stopped");
-                    stopSingleUserLU(userId, stopUserCallback, keyEvictedCallback);
+                    stopSingleUserLU(userId, allowDelayedLocking, stopUserCallback,
+                            keyEvictedCallback);
                     return USER_OP_SUCCESS;
                 }
                 return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
@@ -665,21 +701,64 @@
         }
         if (DEBUG_MU) Slog.i(TAG, "stopUsersLocked usersToStop=" + Arrays.toString(usersToStop));
         for (int userIdToStop : usersToStop) {
-            stopSingleUserLU(userIdToStop,
+            stopSingleUserLU(userIdToStop, allowDelayedLocking,
                     userIdToStop == userId ? stopUserCallback : null,
                     userIdToStop == userId ? keyEvictedCallback : null);
         }
         return USER_OP_SUCCESS;
     }
 
+    /**
+     * Stops a single User. This can also trigger locking user data out depending on device's
+     * config ({@code mDelayUserDataLocking}) and arguments.
+     * User will be unlocked when
+     * - {@code mDelayUserDataLocking} is not set.
+     * - {@code mDelayUserDataLocking} is set and {@code keyEvictedCallback} is non-null.
+     * -
+     *
+     * @param userId User Id to stop and lock the data.
+     * @param allowDelayedLocking When set, do not lock user after stopping. Locking can happen
+     *                            later when number of unlocked users reaches
+     *                            {@code mMaxRunnngUsers}. Note that this is respected only when
+     *                            {@code mDelayUserDataLocking} is set and {@keyEvictedCallback} is
+     *                            null. Otherwise the user will be locked.
+     * @param stopUserCallback Callback to notify that user has stopped.
+     * @param keyEvictedCallback Callback to notify that user has been unlocked.
+     */
     @GuardedBy("mLock")
-    private void stopSingleUserLU(final int userId, final IStopUserCallback stopUserCallback,
+    private void stopSingleUserLU(final int userId, boolean allowDelayedLocking,
+            final IStopUserCallback stopUserCallback,
             KeyEvictedCallback keyEvictedCallback) {
         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 (uss == null) {  // User is not started
+            // If mDelayUserDataLocking is set and allowDelayedLocking is not set, we need to lock
+            // the requested user as the client wants to stop and lock the user. On the other hand,
+            // having keyEvictedCallback set will lead into locking user if mDelayUserDataLocking
+            // is set as that means client wants to lock the user immediately.
+            // If mDelayUserDataLocking is not set, the user was already locked when it was stopped
+            // and no further action is necessary.
+            if (mDelayUserDataLocking) {
+                if (allowDelayedLocking && keyEvictedCallback != null) {
+                    Slog.wtf(TAG, "allowDelayedLocking set with KeyEvictedCallback, ignore it"
+                            + " and lock user:" + userId, new RuntimeException());
+                    allowDelayedLocking = false;
+                }
+                if (!allowDelayedLocking) {
+                    if (mLastActiveUsers.remove(Integer.valueOf(userId))) {
+                        // should lock the user, user is already gone
+                        final ArrayList<KeyEvictedCallback> keyEvictedCallbacks;
+                        if (keyEvictedCallback != null) {
+                            keyEvictedCallbacks = new ArrayList<>(1);
+                            keyEvictedCallbacks.add(keyEvictedCallback);
+                        } else {
+                            keyEvictedCallbacks = null;
+                        }
+                        dispatchUserLocking(userId, keyEvictedCallbacks);
+                    }
+                }
+            }
+            // We do need to post the stopped callback even though user is already stopped.
             if (stopUserCallback != null) {
                 mHandler.post(() -> {
                     try {
@@ -704,6 +783,7 @@
             mInjector.getUserManagerInternal().setUserState(userId, uss.state);
             updateStartedUserArrayLU();
 
+            final boolean allowDelayyLockingCopied = allowDelayedLocking;
             // Post to handler to obtain amLock
             mHandler.post(() -> {
                 // We are going to broadcast ACTION_USER_STOPPING and then
@@ -718,7 +798,8 @@
                     @Override
                     public void performReceive(Intent intent, int resultCode, String data,
                             Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
-                        mHandler.post(() -> finishUserStopping(userId, uss));
+                        mHandler.post(() -> finishUserStopping(userId, uss,
+                                allowDelayyLockingCopied));
                     }
                 };
 
@@ -734,7 +815,8 @@
         }
     }
 
-    void finishUserStopping(final int userId, final UserState uss) {
+    void finishUserStopping(final int userId, final UserState uss,
+            final boolean allowDelayedLocking) {
         Slog.d(TAG, "UserController event: finishUserStopping(" + userId + ")");
         // On to the next.
         final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
@@ -746,7 +828,7 @@
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        finishUserStopped(uss);
+                        finishUserStopped(uss, allowDelayedLocking);
                     }
                 });
             }
@@ -773,7 +855,7 @@
                 Binder.getCallingPid(), userId);
     }
 
-    void finishUserStopped(UserState uss) {
+    void finishUserStopped(UserState uss, boolean allowDelayedLocking) {
         final int userId = uss.mHandle.getIdentifier();
         Slog.d(TAG, "UserController event: finishUserStopped(" + userId + ")");
         final boolean stopped;
@@ -792,7 +874,13 @@
                 mStartedUsers.remove(userId);
                 mUserLru.remove(Integer.valueOf(userId));
                 updateStartedUserArrayLU();
-                userIdToLock = updateUserToLockLU(userId);
+                if (allowDelayedLocking && !keyEvictedCallbacks.isEmpty()) {
+                    Slog.wtf(TAG,
+                            "Delayed locking enabled while KeyEvictedCallbacks not empty, userId:"
+                                    + userId + " callbacks:" + keyEvictedCallbacks);
+                    allowDelayedLocking = false;
+                }
+                userIdToLock = updateUserToLockLU(userId, allowDelayedLocking);
                 if (userIdToLock == UserHandle.USER_NULL) {
                     lockUser = false;
                 }
@@ -826,31 +914,36 @@
             if (!lockUser) {
                 return;
             }
-            final int userIdToLockF = userIdToLock;
-            // Evict the user's credential encryption key. Performed on FgThread to make it
-            // serialized with call to UserManagerService.onBeforeUnlockUser in finishUserUnlocking
-            // to prevent data corruption.
-            FgThread.getHandler().post(() -> {
-                synchronized (mLock) {
-                    if (mStartedUsers.get(userIdToLockF) != null) {
-                        Slog.w(TAG, "User was restarted, skipping key eviction");
-                        return;
-                    }
-                }
-                try {
-                    mInjector.getStorageManager().lockUserKey(userIdToLockF);
-                } catch (RemoteException re) {
-                    throw re.rethrowAsRuntimeException();
-                }
-                if (userIdToLockF == userId) {
-                    for (final KeyEvictedCallback callback : keyEvictedCallbacks) {
-                        callback.keyEvicted(userId);
-                    }
-                }
-            });
+            dispatchUserLocking(userIdToLock, keyEvictedCallbacks);
         }
     }
 
+    private void dispatchUserLocking(@UserIdInt int userId,
+            @Nullable List<KeyEvictedCallback> keyEvictedCallbacks) {
+        // Evict the user's credential encryption key. Performed on FgThread to make it
+        // serialized with call to UserManagerService.onBeforeUnlockUser in finishUserUnlocking
+        // to prevent data corruption.
+        FgThread.getHandler().post(() -> {
+            synchronized (mLock) {
+                if (mStartedUsers.get(userId) != null) {
+                    Slog.w(TAG, "User was restarted, skipping key eviction");
+                    return;
+                }
+            }
+            try {
+                mInjector.getStorageManager().lockUserKey(userId);
+            } catch (RemoteException re) {
+                throw re.rethrowAsRuntimeException();
+            }
+            if (keyEvictedCallbacks == null) {
+                return;
+            }
+            for (int i = 0; i < keyEvictedCallbacks.size(); i++) {
+                keyEvictedCallbacks.get(i).keyEvicted(userId);
+            }
+        });
+    }
+
     /**
      * For mDelayUserDataLocking mode, storage once unlocked is kept unlocked.
      * Total number of unlocked user storage is limited by mMaxRunningUsers.
@@ -861,9 +954,9 @@
      * @return user id to lock. UserHandler.USER_NULL will be returned if no user should be locked.
      */
     @GuardedBy("mLock")
-    private int updateUserToLockLU(@UserIdInt int userId) {
+    private int updateUserToLockLU(@UserIdInt int userId, boolean allowDelayedLocking) {
         int userIdToLock = userId;
-        if (mDelayUserDataLocking && !getUserInfo(userId).isEphemeral()
+        if (mDelayUserDataLocking && allowDelayedLocking && !getUserInfo(userId).isEphemeral()
                 && !hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) {
             mLastActiveUsers.remove((Integer) userId); // arg should be object, not index
             mLastActiveUsers.add(0, userId);
@@ -945,7 +1038,8 @@
         if (userInfo.isGuest() || userInfo.isEphemeral()) {
             // This is a user to be stopped.
             synchronized (mLock) {
-                stopUsersLU(oldUserId, true, null, null);
+                stopUsersLU(oldUserId, /* force= */ true, /* allowDelayedLocking= */ false,
+                        null, null);
             }
         }
     }
@@ -977,7 +1071,7 @@
         }
         final int profilesToStartSize = profilesToStart.size();
         int i = 0;
-        for (; i < profilesToStartSize && i < (mMaxRunningUsers - 1); ++i) {
+        for (; i < profilesToStartSize && i < (getMaxRunningUsers() - 1); ++i) {
             startUser(profilesToStart.get(i).id, /* foreground= */ false);
         }
         if (i < profilesToStartSize) {
@@ -1094,7 +1188,7 @@
                 return false;
             }
 
-            if (foreground && mUserSwitchUiEnabled) {
+            if (foreground && isUserSwitchUiEnabled()) {
                 t.traceBegin("startFreezingScreen");
                 mInjector.getWindowManager().startFreezingScreen(
                         R.anim.screen_user_exit, R.anim.screen_user_enter);
@@ -1142,9 +1236,11 @@
             if (foreground) {
                 // Make sure the old user is no longer considering the display to be on.
                 mInjector.reportGlobalUsageEventLocked(UsageEvents.Event.SCREEN_NON_INTERACTIVE);
+                boolean userSwitchUiEnabled;
                 synchronized (mLock) {
                     mCurrentUserId = userId;
                     mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
+                    userSwitchUiEnabled = mUserSwitchUiEnabled;
                 }
                 mInjector.updateUserConfiguration();
                 updateCurrentProfileIds();
@@ -1152,7 +1248,7 @@
                 mInjector.reportCurWakefulnessUsageEvent();
                 // Once the internal notion of the active user has switched, we lock the device
                 // with the option to show the user switcher on the keyguard.
-                if (mUserSwitchUiEnabled) {
+                if (userSwitchUiEnabled) {
                     mInjector.getWindowManager().setSwitchingUser(true);
                     mInjector.getWindowManager().lockNow(null);
                 }
@@ -1391,10 +1487,12 @@
             Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not a full user");
             return false;
         }
+        boolean userSwitchUiEnabled;
         synchronized (mLock) {
             mTargetUserId = targetUserId;
+            userSwitchUiEnabled = mUserSwitchUiEnabled;
         }
-        if (mUserSwitchUiEnabled) {
+        if (userSwitchUiEnabled) {
             UserInfo currentUserInfo = getUserInfo(currentUserId);
             Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
             mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
@@ -1458,14 +1556,15 @@
         }
         // If running in background is disabled or mDelayUserDataLocking mode, stop the user.
         boolean disallowRunInBg = hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND,
-                oldUserId) || mDelayUserDataLocking;
+                oldUserId) || isDelayUserDataLockingEnabled();
         if (!disallowRunInBg) {
             return;
         }
         synchronized (mLock) {
             if (DEBUG_MU) Slog.i(TAG, "stopBackgroundUsersIfEnforced stopping " + oldUserId
                     + " and related users");
-            stopUsersLU(oldUserId, false, null, null);
+            stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ true,
+                    null, null);
         }
     }
 
@@ -1551,7 +1650,7 @@
 
     void continueUserSwitch(UserState uss, int oldUserId, int newUserId) {
         Slog.d(TAG, "Continue user switch oldUser #" + oldUserId + ", newUser #" + newUserId);
-        if (mUserSwitchUiEnabled) {
+        if (isUserSwitchUiEnabled()) {
             mInjector.getWindowManager().stopFreezingScreen();
         }
         uss.switching = false;
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 79cc3db..f122014 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -31,6 +31,7 @@
 
 import static com.google.android.collect.Lists.newArrayList;
 import static com.google.android.collect.Sets.newHashSet;
+import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertEquals;
@@ -54,6 +55,7 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.app.IUserSwitchObserver;
 import android.content.Context;
 import android.content.IIntentReceiver;
@@ -74,10 +76,10 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.Log;
 
-
 import androidx.test.filters.SmallTest;
 
 import com.android.server.FgThread;
+import com.android.server.am.UserState.KeyEvictedCallback;
 import com.android.server.pm.UserManagerService;
 import com.android.server.wm.WindowManagerService;
 
@@ -107,6 +109,7 @@
     private static final int TEST_USER_ID = 100;
     private static final int TEST_USER_ID1 = 101;
     private static final int TEST_USER_ID2 = 102;
+    private static final int TEST_USER_ID3 = 103;
     private static final int NONEXIST_USER_ID = 2;
     private static final int TEST_PRE_CREATED_USER_ID = 103;
 
@@ -120,6 +123,8 @@
     private TestInjector mInjector;
     private final HashMap<Integer, UserState> mUserStates = new HashMap<>();
 
+    private final KeyEvictedCallback mKeyEvictedCallback = (userId) -> { /* ignore */ };
+
     private static final List<String> START_FOREGROUND_USER_ACTIONS = newArrayList(
             Intent.ACTION_USER_STARTED,
             Intent.ACTION_USER_SWITCHED,
@@ -153,6 +158,7 @@
             doNothing().when(mInjector).activityManagerOnUserStopped(anyInt());
             doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt());
             doNothing().when(mInjector).stackSupervisorRemoveUser(anyInt());
+            // All UserController params are set to default.
             mUserController = new UserController(mInjector);
             setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS);
             setUpUser(TEST_PRE_CREATED_USER_ID, NO_USERINFO_FLAGS, /* preCreated=*/ true);
@@ -187,7 +193,9 @@
 
     @Test
     public void testStartUserUIDisabled() {
-        mUserController.mUserSwitchUiEnabled = false;
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
         mUserController.startUser(TEST_USER_ID, true /* foreground */);
         verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
         verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
@@ -245,7 +253,9 @@
 
     @Test
     public void testFailedStartUserInForeground() {
-        mUserController.mUserSwitchUiEnabled = false;
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
         mUserController.startUserInForeground(NONEXIST_USER_ID);
         verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean());
         verify(mInjector.getWindowManager()).setSwitchingUser(false);
@@ -326,7 +336,9 @@
 
     @Test
     public void testContinueUserSwitchUIDisabled() throws RemoteException {
-        mUserController.mUserSwitchUiEnabled = false;
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
         // Start user -- this will update state of mUserController
         mUserController.startUser(TEST_USER_ID, true);
         Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -389,11 +401,13 @@
      * Test stopping of user from max running users limit.
      */
     @Test
-    public void testUserStoppingForMultipleUsersNormalMode()
+    public void testUserLockingFromUserSwitchingForMultipleUsersNonDelayedLocking()
             throws InterruptedException, RemoteException {
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
         setUpUser(TEST_USER_ID1, 0);
         setUpUser(TEST_USER_ID2, 0);
-        mUserController.mMaxRunningUsers = 3;
         int numerOfUserSwitches = 1;
         addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
                 numerOfUserSwitches, false);
@@ -430,12 +444,13 @@
      * all middle steps which takes too much work to mock.
      */
     @Test
-    public void testUserStoppingForMultipleUsersDelayedLockingMode()
+    public void testUserLockingFromUserSwitchingForMultipleUsersDelayedLockingMode()
             throws InterruptedException, RemoteException {
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
+
         setUpUser(TEST_USER_ID1, 0);
         setUpUser(TEST_USER_ID2, 0);
-        mUserController.mMaxRunningUsers = 3;
-        mUserController.mDelayUserDataLocking = true;
         int numerOfUserSwitches = 1;
         addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
                 numerOfUserSwitches, false);
@@ -456,7 +471,7 @@
         // Skip all other steps and test unlock delaying only
         UserState uss = mUserStates.get(TEST_USER_ID);
         uss.setState(UserState.STATE_SHUTDOWN); // necessary state change from skipped part
-        mUserController.finishUserStopped(uss);
+        mUserController.finishUserStopped(uss, /* allowDelayedLocking= */ true);
         // Cannot mock FgThread handler, so confirm that there is no posted message left before
         // checking.
         waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
@@ -473,12 +488,87 @@
                 mUserController.getRunningUsersLU());
         UserState ussUser1 = mUserStates.get(TEST_USER_ID1);
         ussUser1.setState(UserState.STATE_SHUTDOWN);
-        mUserController.finishUserStopped(ussUser1);
+        mUserController.finishUserStopped(ussUser1, /* allowDelayedLocking= */ true);
         waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
         verify(mInjector.mStorageManagerMock, times(1))
                 .lockUserKey(TEST_USER_ID);
     }
 
+    /**
+     * Test locking user with mDelayUserDataLocking false.
+     */
+    @Test
+    public void testUserLockingWithStopUserForNonDelayedLockingMode() throws Exception {
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
+        setUpAndStartUserInBackground(TEST_USER_ID);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* delayedLocking= */ true,
+                /* keyEvictedCallback= */ null, /* expectLocking= */ true);
+
+        setUpAndStartUserInBackground(TEST_USER_ID1);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+                /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+
+        setUpAndStartUserInBackground(TEST_USER_ID2);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ false,
+                /* keyEvictedCallback= */ null, /* expectLocking= */ true);
+
+        setUpAndStartUserInBackground(TEST_USER_ID3);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* delayedLocking= */ false,
+                /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+    }
+
+    /**
+     * Test conditional delayed locking with mDelayUserDataLocking true.
+     */
+    @Test
+    public void testUserLockingForDelayedLockingMode() throws Exception {
+        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+                /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
+
+        // delayedLocking set and no KeyEvictedCallback, so it should not lock.
+        setUpAndStartUserInBackground(TEST_USER_ID);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* delayedLocking= */ true,
+                /* keyEvictedCallback= */ null, /* expectLocking= */ false);
+
+        setUpAndStartUserInBackground(TEST_USER_ID1);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+                /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+
+        setUpAndStartUserInBackground(TEST_USER_ID2);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ false,
+                /* keyEvictedCallback= */ null, /* expectLocking= */ true);
+
+        setUpAndStartUserInBackground(TEST_USER_ID3);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* delayedLocking= */ false,
+                /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+    }
+
+    private void setUpAndStartUserInBackground(int userId) throws Exception {
+        setUpUser(userId, 0);
+        mUserController.startUser(userId, /* foreground= */ false);
+        verify(mInjector.mStorageManagerMock, times(1))
+                .unlockUserKey(TEST_USER_ID, 0, null, null);
+        mUserStates.put(userId, mUserController.getStartedUserState(userId));
+    }
+
+    private void assertUserLockedOrUnlockedAfterStopping(int userId, boolean delayedLocking,
+            KeyEvictedCallback keyEvictedCallback, boolean expectLocking)  throws Exception {
+        int r = mUserController.stopUser(userId, /* force= */ true, /* delayedLocking= */
+                delayedLocking, null, keyEvictedCallback);
+        assertThat(r).isEqualTo(ActivityManager.USER_OP_SUCCESS);
+        // fake all interim steps
+        UserState ussUser = mUserStates.get(userId);
+        ussUser.setState(UserState.STATE_SHUTDOWN);
+        // Passing delayedLocking invalidates incorrect internal data passing but currently there is
+        // no easy way to get that information passed through lambda.
+        mUserController.finishUserStopped(ussUser, delayedLocking);
+        waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
+        verify(mInjector.mStorageManagerMock, times(expectLocking ? 1 : 0))
+                .lockUserKey(userId);
+    }
+
     private void addForegroundUserAndContinueUserSwitch(int newUserId, int expectedOldUserId,
             int expectedNumberOfCalls, boolean expectOldUserStopping)
             throws RemoteException {