Evict CE key on request and when work mode is turned off.

DPMS.lockNow takes a flag which can request the managed profile CE key to
be evicted.

Test: com.android.cts.devicepolicy.ManagedProfileTest#testLockNowWithKeyEviction*
Bug: 31000719
Change-Id: I68f4d6eed4b041c39fd13375f7f284f5d6ac33da
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 4d6ffe6..a5552b8 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -176,12 +176,8 @@
         }
 
         @Override
-        public void onBootPhase(int phase) {
-            if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
-                mLockSettingsService.maybeShowEncryptionNotifications();
-            } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
-                // TODO
-            }
+        public void onStartUser(int userHandle) {
+            mLockSettingsService.onStartUser(userHandle);
         }
 
         @Override
@@ -313,27 +309,25 @@
      * If the account is credential-encrypted, show notification requesting the user to unlock
      * the device.
      */
-    private void maybeShowEncryptionNotifications() {
-        final List<UserInfo> users = mUserManager.getUsers();
-        for (int i = 0; i < users.size(); i++) {
-            UserInfo user = users.get(i);
-            UserHandle userHandle = user.getUserHandle();
-            final boolean isSecure = mStorage.hasPassword(user.id) || mStorage.hasPattern(user.id);
-            if (isSecure && !mUserManager.isUserUnlockingOrUnlocked(userHandle)) {
-                if (!user.isManagedProfile()) {
-                    // When the user is locked, we communicate it loud-and-clear
-                    // on the lockscreen; we only show a notification below for
-                    // locked managed profiles.
-                } else {
-                    UserInfo parent = mUserManager.getProfileParent(user.id);
-                    if (parent != null &&
-                            mUserManager.isUserUnlockingOrUnlocked(parent.getUserHandle()) &&
-                            !mUserManager.isQuietModeEnabled(userHandle)) {
-                        // Only show notifications for managed profiles once their parent
-                        // user is unlocked.
-                        showEncryptionNotificationForProfile(userHandle);
-                    }
-                }
+    private void maybeShowEncryptionNotificationForUser(@UserIdInt int userId) {
+        final UserInfo user = mUserManager.getUserInfo(userId);
+        if (!user.isManagedProfile()) {
+            // When the user is locked, we communicate it loud-and-clear
+            // on the lockscreen; we only show a notification below for
+            // locked managed profiles.
+            return;
+        }
+
+        final UserHandle userHandle = user.getUserHandle();
+        final boolean isSecure = mStorage.hasPassword(userId) || mStorage.hasPattern(userId);
+        if (isSecure && !mUserManager.isUserUnlockingOrUnlocked(userHandle)) {
+            UserInfo parent = mUserManager.getProfileParent(userId);
+            if (parent != null &&
+                    mUserManager.isUserUnlockingOrUnlocked(parent.getUserHandle()) &&
+                    !mUserManager.isQuietModeEnabled(userHandle)) {
+                // Only show notifications for managed profiles once their parent
+                // user is unlocked.
+                showEncryptionNotificationForProfile(userHandle);
             }
         }
     }
@@ -384,7 +378,7 @@
         mNotificationManager.notifyAsUser(null, FBE_ENCRYPTED_NOTIFICATION, notification, user);
     }
 
-    public void hideEncryptionNotification(UserHandle userHandle) {
+    private void hideEncryptionNotification(UserHandle userHandle) {
         if (DEBUG) Slog.v(TAG, "hide encryption notification, user: "+ userHandle.getIdentifier());
         mNotificationManager.cancelAsUser(null, FBE_ENCRYPTED_NOTIFICATION, userHandle);
     }
@@ -393,6 +387,10 @@
         hideEncryptionNotification(new UserHandle(userId));
     }
 
+    public void onStartUser(final int userId) {
+        maybeShowEncryptionNotificationForUser(userId);
+    }
+
     public void onUnlockUser(final int userId) {
         // Hide notification first, as tie managed profile lock takes time
         hideEncryptionNotification(new UserHandle(userId));
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index eae4905..f7c0275 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -22566,6 +22566,11 @@
         }
     }
 
+    @Override
+    public int restartUserInBackground(final int userId) {
+        return mUserController.restartUser(userId, /* foreground */ false);
+    }
+
     /**
      * Attach an agent to the specified process (proces name or PID)
      */
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index a0a04bb..45e06b0 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -442,6 +442,19 @@
         }
     }
 
+    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) {
@@ -634,6 +647,12 @@
         }
 
         if (stopped) {
+            // Evict the user's credential encryption key
+            try {
+                getStorageManager().lockUserKey(userId);
+            } catch (RemoteException re) {
+                throw re.rethrowAsRuntimeException();
+            }
             mInjector.systemServiceManagerCleanupUser(userId);
             synchronized (mLock) {
                 mInjector.stackSupervisorRemoveUserLocked(userId);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 05228ec..9b47beb 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -26,6 +26,7 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerNative;
 import android.app.AppGlobals;
 import android.app.IActivityManager;
 import android.app.IStopUserCallback;
@@ -857,6 +858,25 @@
         }
     }
 
+    /**
+     * Evicts a user's CE key by stopping and restarting the user.
+     *
+     * The key is evicted automatically by the user controller when the user has stopped.
+     */
+    @Override
+    public void evictCredentialEncryptionKey(@UserIdInt int userId) {
+        checkManageUsersPermission("evict CE key");
+        final IActivityManager am = ActivityManagerNative.getDefault();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            am.restartUserInBackground(userId);
+        } catch (RemoteException re) {
+            throw re.rethrowAsRuntimeException();
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
     @Override
     public UserInfo getUserInfo(int userId) {
         checkManageOrCreateUsersPermission("query user");
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index cca8cc8..c69b87c 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -25,6 +25,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import android.Manifest;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.ITrustListener;
@@ -103,6 +104,7 @@
     private static final int MSG_SWITCH_USER = 9;
     private static final int MSG_FLUSH_TRUST_USUALLY_MANAGED = 10;
     private static final int MSG_UNLOCK_USER = 11;
+    private static final int MSG_STOP_USER = 12;
 
     private static final int TRUST_USUALLY_MANAGED_FLUSH_DELAY = 2 * 60 * 1000;
 
@@ -414,15 +416,18 @@
                 }
             }
             boolean deviceLocked = secure && showingKeyguard && !trusted;
+            setDeviceLockedForUser(id, deviceLocked);
+        }
+    }
 
-            boolean changed;
-            synchronized (mDeviceLockedForUser) {
-                changed = isDeviceLockedInner(id) != deviceLocked;
-                mDeviceLockedForUser.put(id, deviceLocked);
-            }
-            if (changed) {
-                dispatchDeviceLocked(id, deviceLocked);
-            }
+    private void setDeviceLockedForUser(@UserIdInt int userId, boolean locked) {
+        final boolean changed;
+        synchronized (mDeviceLockedForUser) {
+            changed = isDeviceLockedInner(userId) != locked;
+            mDeviceLockedForUser.put(userId, locked);
+        }
+        if (changed) {
+            dispatchDeviceLocked(userId, locked);
         }
     }
 
@@ -724,6 +729,11 @@
         mHandler.obtainMessage(MSG_UNLOCK_USER, userId, 0, null).sendToTarget();
     }
 
+    @Override
+    public void onStopUser(@UserIdInt int userId) {
+        mHandler.obtainMessage(MSG_STOP_USER, userId, 0, null).sendToTarget();
+    }
+
     // Plumbing
 
     private final IBinder mService = new ITrustManager.Stub() {
@@ -982,6 +992,9 @@
                     mCurrentUser = msg.arg1;
                     refreshDeviceLockedForUser(UserHandle.USER_ALL);
                     break;
+                case MSG_STOP_USER:
+                    setDeviceLockedForUser(msg.arg1, true);
+                    break;
                 case MSG_FLUSH_TRUST_USUALLY_MANAGED:
                     SparseBooleanArray usuallyManaged;
                     synchronized (mTrustUsuallyManagedForUser) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 050f25d..6233d08 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -75,6 +75,7 @@
 import android.app.admin.SecurityLog.SecurityEvent;
 import android.app.admin.SystemUpdatePolicy;
 import android.app.backup.IBackupManager;
+import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -1496,6 +1497,10 @@
             return TelephonyManager.from(mContext);
         }
 
+        TrustManager getTrustManager() {
+            return (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
+        }
+
         IWindowManager getIWindowManager() {
             return IWindowManager.Stub
                     .asInterface(ServiceManager.getService(Context.WINDOW_SERVICE));
@@ -4377,31 +4382,52 @@
     }
 
     @Override
-    public void lockNow(boolean parent) {
+    public void lockNow(int flags, boolean parent) {
         if (!mHasFeature) {
             return;
         }
+
+        final int callingUserId = mInjector.userHandleGetCallingUserId();
         synchronized (this) {
             // This API can only be called by an active device admin,
             // so try to retrieve it to check that the caller is one.
-            getActiveAdminForCallerLocked(
+            final ActiveAdmin admin = getActiveAdminForCallerLocked(
                     null, DeviceAdminInfo.USES_POLICY_FORCE_LOCK, parent);
 
-            int userToLock = mInjector.userHandleGetCallingUserId();
-
-            // Unless this is a managed profile with work challenge enabled, lock all users.
-            if (parent || !isSeparateProfileChallengeEnabled(userToLock)) {
-                userToLock = UserHandle.USER_ALL;
-            }
             final long ident = mInjector.binderClearCallingIdentity();
             try {
+                // Evict key
+                if ((flags & DevicePolicyManager.FLAG_EVICT_CE_KEY) != 0) {
+                    enforceManagedProfile(callingUserId, "set FLAG_EVICT_CE_KEY");
+                    if (!isProfileOwner(admin.info.getComponent(), callingUserId)) {
+                        throw new SecurityException(
+                               "Only profile owner admins can set FLAG_EVICT_CE_KEY");
+                    }
+                    if (parent) {
+                        throw new IllegalArgumentException(
+                                "Cannot set FLAG_EVICT_CE_KEY for the parent");
+                    }
+                    if (!mInjector.storageManagerIsFileBasedEncryptionEnabled()) {
+                        throw new UnsupportedOperationException(
+                                "FLAG_EVICT_CE_KEY only applies to FBE devices");
+                    }
+                    mUserManager.evictCredentialEncryptionKey(callingUserId);
+                }
+
+                // Lock all users unless this is a managed profile with a separate challenge
+                final int userToLock = (parent || !isSeparateProfileChallengeEnabled(callingUserId)
+                        ? UserHandle.USER_ALL : callingUserId);
                 mLockPatternUtils.requireStrongAuth(
                         STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW, userToLock);
+
+                // Require authentication for the device or profile
                 if (userToLock == UserHandle.USER_ALL) {
                     // Power off the display
                     mInjector.powerManagerGoToSleep(SystemClock.uptimeMillis(),
                             PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN, 0);
                     mInjector.getIWindowManager().lockNow(null);
+                } else {
+                    mInjector.getTrustManager().setDeviceLockedForUser(userToLock, true);
                 }
             } catch (RemoteException e) {
             } finally {