Merge "Offload user-switching task from startInputOrWindowGainedFocus()"
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 1e6abd9..39d5f5c 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1008,6 +1008,13 @@
         }
 
         @Override
+        public void scheduleStartInputIfNecessary(boolean fullscreen) {
+            // TODO(b/149859205): See if we can optimize this by having a fused dedicated operation.
+            mH.obtainMessage(MSG_SET_ACTIVE, 0 /* active */, fullscreen ? 1 : 0).sendToTarget();
+            mH.obtainMessage(MSG_SET_ACTIVE, 1 /* active */, fullscreen ? 1 : 0).sendToTarget();
+        }
+
+        @Override
         public void reportFullscreenMode(boolean fullscreen) {
             mH.obtainMessage(MSG_REPORT_FULLSCREEN_MODE, fullscreen ? 1 : 0, 0)
                     .sendToTarget();
diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl
index 41f902e..4509032 100644
--- a/core/java/com/android/internal/view/IInputMethodClient.aidl
+++ b/core/java/com/android/internal/view/IInputMethodClient.aidl
@@ -28,6 +28,7 @@
     void onBindMethod(in InputBindResult res);
     void onUnbindMethod(int sequence, int unbindReason);
     void setActive(boolean active, boolean fullscreen);
+    void scheduleStartInputIfNecessary(boolean fullscreen);
     void reportFullscreenMode(boolean fullscreen);
     void reportPreRendered(in EditorInfo info);
     void applyImeVisibility(boolean setVisible);
diff --git a/core/java/com/android/internal/view/InputBindResult.java b/core/java/com/android/internal/view/InputBindResult.java
index a5964b5..f29e95c 100644
--- a/core/java/com/android/internal/view/InputBindResult.java
+++ b/core/java/com/android/internal/view/InputBindResult.java
@@ -89,57 +89,64 @@
          */
         int SUCCESS_WAITING_IME_BINDING = 2;
         /**
+         * Indicates that {@link com.android.server.inputmethod.InputMethodManagerService} has a
+         * pending operation to switch to a different user.
+         *
+         * <p>Note that in this state even what would be the next current IME is not determined.</p>
+         */
+        int SUCCESS_WAITING_USER_SWITCHING = 3;
+        /**
          * Indicates that this is not intended for starting input but just for reporting window
          * focus change from the application process.
          *
          * <p>All other fields do not have meaningful value.</p>
          */
-        int SUCCESS_REPORT_WINDOW_FOCUS_ONLY = 3;
+        int SUCCESS_REPORT_WINDOW_FOCUS_ONLY = 4;
         /**
          * Indicates somehow
          * {@link
          * com.android.server.inputmethod.InputMethodManagerService#startInputOrWindowGainedFocus}
          * is trying to return null {@link InputBindResult}, which must never happen.
          */
-        int ERROR_NULL = 4;
+        int ERROR_NULL = 5;
         /**
          * Indicates that {@link com.android.server.inputmethod.InputMethodManagerService}
          * recognizes no IME.
          */
-        int ERROR_NO_IME = 5;
+        int ERROR_NO_IME = 6;
         /**
          * Indicates that {@link android.view.inputmethod.EditorInfo#packageName} does not match
          * the caller UID.
          *
          * @see android.view.inputmethod.EditorInfo#packageName
          */
-        int ERROR_INVALID_PACKAGE_NAME = 6;
+        int ERROR_INVALID_PACKAGE_NAME = 7;
         /**
          * Indicates that the system is still in an early stage of the boot process and any 3rd
          * party application is not allowed to run.
          *
          * @see com.android.server.SystemService#PHASE_THIRD_PARTY_APPS_CAN_START
          */
-        int ERROR_SYSTEM_NOT_READY = 7;
+        int ERROR_SYSTEM_NOT_READY = 8;
         /**
          * Indicates that {@link com.android.server.inputmethod.InputMethodManagerService} tried to
          * connect to an {@link android.inputmethodservice.InputMethodService} but failed.
          *
          * @see android.content.Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle)
          */
-        int ERROR_IME_NOT_CONNECTED = 8;
+        int ERROR_IME_NOT_CONNECTED = 9;
         /**
          * Indicates that the caller is not the foreground user, does not have
          * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission, or the user
          * specified in {@link android.view.inputmethod.EditorInfo#targetInputMethodUser} is not
          * running.
          */
-        int ERROR_INVALID_USER = 9;
+        int ERROR_INVALID_USER = 10;
         /**
          * Indicates that the caller should have specified non-null
          * {@link android.view.inputmethod.EditorInfo}.
          */
-        int ERROR_NULL_EDITOR_INFO = 10;
+        int ERROR_NULL_EDITOR_INFO = 11;
         /**
          * Indicates that the target window the client specified cannot be the IME target right now.
          *
@@ -149,24 +156,24 @@
          *
          * @see com.android.server.wm.WindowManagerInternal#isInputMethodClientFocus(int, int, int)
          */
-        int ERROR_NOT_IME_TARGET_WINDOW = 11;
+        int ERROR_NOT_IME_TARGET_WINDOW = 12;
         /**
          * Indicates that focused view in the current window is not an editor.
          */
-        int ERROR_NO_EDITOR = 12;
+        int ERROR_NO_EDITOR = 13;
         /**
          * Indicates that there is a mismatch in display ID between IME client and focused Window.
          */
-        int ERROR_DISPLAY_ID_MISMATCH = 13;
+        int ERROR_DISPLAY_ID_MISMATCH = 14;
         /**
          * Indicates that current IME client is no longer allowed to access to the associated
          * display.
          */
-        int ERROR_INVALID_DISPLAY_ID = 14;
+        int ERROR_INVALID_DISPLAY_ID = 15;
         /**
          * Indicates that the client is not recognized by the system.
          */
-        int ERROR_INVALID_CLIENT = 15;
+        int ERROR_INVALID_CLIENT = 16;
     }
 
     @ResultCode
@@ -299,6 +306,8 @@
                 return "SUCCESS_WAITING_IME_SESSION";
             case ResultCode.SUCCESS_WAITING_IME_BINDING:
                 return "SUCCESS_WAITING_IME_BINDING";
+            case ResultCode.SUCCESS_WAITING_USER_SWITCHING:
+                return "SUCCESS_WAITING_USER_SWITCHING";
             case ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY:
                 return "SUCCESS_REPORT_WINDOW_FOCUS_ONLY";
             case ResultCode.ERROR_NULL:
@@ -386,4 +395,11 @@
      * Predefined error object for {@link ResultCode#ERROR_INVALID_CLIENT}.
      */
     public static final InputBindResult INVALID_CLIENT = error(ResultCode.ERROR_INVALID_CLIENT);
+
+    /**
+     * Predefined <strong>success</strong> object for
+     * {@link ResultCode#SUCCESS_WAITING_USER_SWITCHING}.
+     */
+    public static final InputBindResult USER_SWITCHING =
+            error(ResultCode.SUCCESS_WAITING_USER_SWITCHING);
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index d7cb192..87262a8 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1388,6 +1388,44 @@
         }
     }
 
+    private static final class UserSwitchHandlerTask implements Runnable {
+        final InputMethodManagerService mService;
+
+        @UserIdInt
+        final int mToUserId;
+
+        @Nullable
+        IInputMethodClient mClientToBeReset;
+
+        UserSwitchHandlerTask(InputMethodManagerService service, @UserIdInt int toUserId,
+                @Nullable IInputMethodClient clientToBeReset) {
+            mService = service;
+            mToUserId = toUserId;
+            mClientToBeReset = clientToBeReset;
+        }
+
+        @Override
+        public void run() {
+            synchronized (mService.mMethodMap) {
+                if (mService.mUserSwitchHandlerTask != this) {
+                    // This task was already canceled before it is handled here. So do nothing.
+                    return;
+                }
+                mService.switchUserOnHandlerLocked(mService.mUserSwitchHandlerTask.mToUserId,
+                        mClientToBeReset);
+                mService.mUserSwitchHandlerTask = null;
+            }
+        }
+    }
+
+    /**
+     * When non-{@code null}, this represents pending user-switch task, which is to be executed as
+     * a handler callback.  This needs to be set and unset only within the lock.
+     */
+    @Nullable
+    @GuardedBy("mMethodMap")
+    private UserSwitchHandlerTask mUserSwitchHandlerTask;
+
     public static final class Lifecycle extends SystemService {
         private InputMethodManagerService mService;
 
@@ -1406,8 +1444,9 @@
         @Override
         public void onSwitchUser(@UserIdInt int userHandle) {
             // Called on ActivityManager thread.
-            // TODO: Dispatch this to a worker thread as needed.
-            mService.onSwitchUser(userHandle);
+            synchronized (mService.mMethodMap) {
+                mService.scheduleSwitchUserTaskLocked(userHandle, null /* clientToBeReset */);
+            }
         }
 
         @Override
@@ -1447,10 +1486,20 @@
         }
     }
 
-    void onSwitchUser(@UserIdInt int userId) {
-        synchronized (mMethodMap) {
-            switchUserLocked(userId);
+    @GuardedBy("mMethodMap")
+    void scheduleSwitchUserTaskLocked(@UserIdInt int userId,
+            @Nullable IInputMethodClient clientToBeReset) {
+        if (mUserSwitchHandlerTask != null) {
+            if (mUserSwitchHandlerTask.mToUserId == userId) {
+                mUserSwitchHandlerTask.mClientToBeReset = clientToBeReset;
+                return;
+            }
+            mHandler.removeCallbacks(mUserSwitchHandlerTask);
         }
+        final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId,
+                clientToBeReset);
+        mUserSwitchHandlerTask = task;
+        mHandler.post(task);
     }
 
     public InputMethodManagerService(Context context) {
@@ -1538,7 +1587,8 @@
     }
 
     @GuardedBy("mMethodMap")
-    private void switchUserLocked(int newUserId) {
+    private void switchUserOnHandlerLocked(@UserIdInt int newUserId,
+            IInputMethodClient clientToBeReset) {
         if (DEBUG) Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId
                 + " currentUserId=" + mSettings.getCurrentUserId());
 
@@ -1589,6 +1639,18 @@
                 + " selectedIme=" + mSettings.getSelectedInputMethod());
 
         mLastSwitchUserId = newUserId;
+
+        if (mIsInteractive && clientToBeReset != null) {
+            final ClientState cs = mClients.get(clientToBeReset.asBinder());
+            if (cs == null) {
+                // The client is already gone.
+                return;
+            }
+            try {
+                cs.client.scheduleStartInputIfNecessary(mInFullscreenMode);
+            } catch (RemoteException e) {
+            }
+        }
     }
 
     void updateCurrentProfileIds() {
@@ -3072,6 +3134,22 @@
             return InputBindResult.NOT_IME_TARGET_WINDOW;
         }
 
+        if (mUserSwitchHandlerTask != null) {
+            // There is already an on-going pending user switch task.
+            final int nextUserId = mUserSwitchHandlerTask.mToUserId;
+            if (userId == nextUserId) {
+                scheduleSwitchUserTaskLocked(userId, cs.client);
+                return InputBindResult.USER_SWITCHING;
+            }
+            for (int profileId : mUserManager.getProfileIdsWithDisabled(nextUserId)) {
+                if (profileId == userId) {
+                    scheduleSwitchUserTaskLocked(userId, cs.client);
+                    return InputBindResult.USER_SWITCHING;
+                }
+            }
+            return InputBindResult.INVALID_USER;
+        }
+
         // cross-profile access is always allowed here to allow profile-switching.
         if (!mSettings.isCurrentProfile(userId)) {
             Slog.w(TAG, "A background user is requesting window. Hiding IME.");
@@ -3083,8 +3161,10 @@
         }
 
         if (userId != mSettings.getCurrentUserId()) {
-            switchUserLocked(userId);
+            scheduleSwitchUserTaskLocked(userId, cs.client);
+            return InputBindResult.USER_SWITCHING;
         }
+
         // Master feature flag that overrides other conditions and forces IME preRendering.
         if (DEBUG) {
             Slog.v(TAG, "IME PreRendering MASTER flag: "