Fix issue #6048808: sometimes auto-correct is inactive

My previous change to speed up the time the IME is dismissed was
fundamentally flawed.  That change basically switched the order
the application called the input method manager service from doing
startInput() and then windowGainedFocus(), to first windowGainedFocus()
and then startInput().

The problem is that the service relies on startInput() being done
first, since this is the mechanism to set up the new input focus,
and windowGainedFocus() is just updating the IME visibility state
after that is done.  However, by doing the startInput() first, that
means in the case where we are going to hide the IME we must first
wait for the IME to re-initialize editing on whatever input has
focus in the new window.

To address this, the change here tries to find a half-way point
between the two.  We now do startInput() after windowGainedFocus()
only when this will result in the window being hidden.

It is not as easy as that, though, because these are calls on to
the system service from the application.  So being able to do that
meant a fair amount of re-arranging of this part of the protocol
with the service.  Now windowGainedFocus() is called with all of
the information also needed for startInput(), and takes care of
performing both operations.  The client-side code is correspondingly
rearranged so that the guts of it where startInput() is called can
instead call the windowGainedFocus() entry if appropriate.

So...  in theory this is safer than the previous change, since it
should not be impacting the behavior as much.  In practice, however,
we are touching and re-arranging a lot more code, and "should" is
not a promise.

Change-Id: Icb58bef75ef4bf9979f3e2ba88cea20db2e2c3fb
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 925e781..ce36046 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -26,7 +26,6 @@
 import com.android.internal.view.InputBindResult;
 
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
@@ -198,7 +197,31 @@
 
     static final Object mInstanceSync = new Object();
     static InputMethodManager mInstance;
-    
+
+    /**
+     * @hide Flag for IInputMethodManager.windowGainedFocus: a view in
+     * the window has input focus.
+     */
+    public static final int CONTROL_WINDOW_VIEW_HAS_FOCUS = 1<<0;
+
+    /**
+     * @hide Flag for IInputMethodManager.windowGainedFocus: the focus
+     * is a text editor.
+     */
+    public static final int CONTROL_WINDOW_IS_TEXT_EDITOR = 1<<1;
+
+    /**
+     * @hide Flag for IInputMethodManager.windowGainedFocus: this is the first
+     * time the window has gotten focus.
+     */
+    public static final int CONTROL_WINDOW_FIRST = 1<<2;
+
+    /**
+     * @hide Flag for IInputMethodManager.startInput: this is the first
+     * time the window has gotten focus.
+     */
+    public static final int CONTROL_START_INITIAL = 1<<8;
+
     final IInputMethodManager mService;
     final Looper mMainLooper;
     
@@ -216,7 +239,7 @@
     
     /**
      * Set whenever this client becomes inactive, to know we need to reset
-     * state with the IME then next time we receive focus.
+     * state with the IME the next time we receive focus.
      */
     boolean mHasBeenInactive = true;
     
@@ -243,11 +266,6 @@
      */
     View mNextServedView;
     /**
-     * True if we should restart input in the next served view, even if the
-     * view hasn't actually changed from the current serve view.
-     */
-    boolean mNextServedNeedsStart;
-    /**
      * This is set when we are in the process of connecting, to determine
      * when we have actually finished.
      */
@@ -331,7 +349,7 @@
                         mCurId = res.id;
                         mBindSequence = res.sequence;
                     }
-                    startInputInner();
+                    startInputInner(null, 0, 0, 0);
                     return;
                 }
                 case MSG_UNBIND: {
@@ -362,7 +380,7 @@
                         }
                     }
                     if (startInput) {
-                        startInputInner();
+                        startInputInner(null, 0, 0, 0);
                     }
                     return;
                 }
@@ -952,10 +970,11 @@
             mServedConnecting = true;
         }
         
-        startInputInner();
+        startInputInner(null, 0, 0, 0);
     }
     
-    void startInputInner() {
+    boolean startInputInner(IBinder windowGainingFocus, int controlFlags, int softInputMode,
+            int windowFlags) {
         final View view;
         synchronized (mH) {
             view = mServedView;
@@ -964,7 +983,7 @@
             if (DEBUG) Log.v(TAG, "Starting input: view=" + view);
             if (view == null) {
                 if (DEBUG) Log.v(TAG, "ABORT input: no served view!");
-                return;
+                return false;
             }
         }
         
@@ -977,7 +996,7 @@
             // If the view doesn't have a handler, something has changed out
             // from under us, so just bail.
             if (DEBUG) Log.v(TAG, "ABORT input: no handler for view!");
-            return;
+            return false;
         }
         if (vh.getLooper() != Looper.myLooper()) {
             // The view is running on a different thread than our own, so
@@ -985,10 +1004,10 @@
             if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread");
             vh.post(new Runnable() {
                 public void run() {
-                    startInputInner();
+                    startInputInner(null, 0, 0, 0);
                 }
             });
-            return;
+            return false;
         }
         
         // Okay we are now ready to call into the served view and have it
@@ -1008,12 +1027,14 @@
                 if (DEBUG) Log.v(TAG, 
                         "Starting input: finished by someone else (view="
                         + mServedView + " conn=" + mServedConnecting + ")");
-                return;
+                return false;
             }
-            
+
             // If we already have a text box, then this view is already
             // connected so we want to restart it.
-            final boolean initial = mCurrentTextBoxAttribute == null;
+            if (mCurrentTextBoxAttribute == null) {
+                controlFlags |= CONTROL_START_INITIAL;
+            }
             
             // Hook 'em up and let 'er rip.
             mCurrentTextBoxAttribute = tba;
@@ -1033,9 +1054,17 @@
             
             try {
                 if (DEBUG) Log.v(TAG, "START INPUT: " + view + " ic="
-                        + ic + " tba=" + tba + " initial=" + initial);
-                InputBindResult res = mService.startInput(mClient,
-                        servedContext, tba, initial, true);
+                        + ic + " tba=" + tba + " controlFlags=#"
+                        + Integer.toHexString(controlFlags));
+                InputBindResult res;
+                if (windowGainingFocus != null) {
+                    res = mService.windowGainedFocus(mClient, windowGainingFocus,
+                            controlFlags, softInputMode, windowFlags,
+                            tba, servedContext);
+                } else {
+                    res = mService.startInput(mClient,
+                            servedContext, tba, controlFlags);
+                }
                 if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
                 if (res != null) {
                     if (res.id != null) {
@@ -1044,7 +1073,7 @@
                     } else if (mCurMethod == null) {
                         // This means there is no input method available.
                         if (DEBUG) Log.v(TAG, "ABORT input: no input method!");
-                        return;
+                        return true;
                     }
                 }
                 if (mCurMethod != null && mCompletions != null) {
@@ -1057,6 +1086,8 @@
                 Log.w(TAG, "IME died: " + mCurId, e);
             }
         }
+
+        return true;
     }
 
     /**
@@ -1133,27 +1164,26 @@
      * @hide
      */
     public void checkFocus() {
-        if (checkFocusNoStartInput()) {
-            startInputInner();
+        if (checkFocusNoStartInput(false)) {
+            startInputInner(null, 0, 0, 0);
         }
     }
 
-    private boolean checkFocusNoStartInput() {
+    private boolean checkFocusNoStartInput(boolean forceNewFocus) {
         // This is called a lot, so short-circuit before locking.
-        if (mServedView == mNextServedView && !mNextServedNeedsStart) {
+        if (mServedView == mNextServedView && !forceNewFocus) {
             return false;
         }
 
         InputConnection ic = null;
         synchronized (mH) {
-            if (mServedView == mNextServedView && !mNextServedNeedsStart) {
+            if (mServedView == mNextServedView && !forceNewFocus) {
                 return false;
             }
             if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView
                     + " next=" + mNextServedView
-                    + " restart=" + mNextServedNeedsStart);
+                    + " forceNewFocus=" + forceNewFocus);
 
-            mNextServedNeedsStart = false;
             if (mNextServedView == null) {
                 finishInputLocked();
                 // In this case, we used to have a focused view on the window,
@@ -1184,13 +1214,14 @@
         } catch (RemoteException e) {
         }
     }
-    
+
     /**
      * Called by ViewAncestor when its window gets input focus.
      * @hide
      */
     public void onWindowFocus(View rootView, View focusedView, int softInputMode,
             boolean first, int windowFlags) {
+        boolean forceNewFocus = false;
         synchronized (mH) {
             if (DEBUG) Log.v(TAG, "onWindowFocus: " + focusedView
                     + " softInputMode=" + softInputMode
@@ -1199,26 +1230,41 @@
             if (mHasBeenInactive) {
                 if (DEBUG) Log.v(TAG, "Has been inactive!  Starting fresh");
                 mHasBeenInactive = false;
-                mNextServedNeedsStart = true;
+                forceNewFocus = true;
             }
             focusInLocked(focusedView != null ? focusedView : rootView);
         }
-        
-        boolean startInput = checkFocusNoStartInput();
-        
-        synchronized (mH) {
-            try {
-                final boolean isTextEditor = focusedView != null &&
-                        focusedView.onCheckIsTextEditor();
-                mService.windowGainedFocus(mClient, rootView.getWindowToken(),
-                        focusedView != null, isTextEditor, softInputMode, first,
-                        windowFlags);
-            } catch (RemoteException e) {
+
+        int controlFlags = 0;
+        if (focusedView != null) {
+            controlFlags |= CONTROL_WINDOW_VIEW_HAS_FOCUS;
+            if (focusedView.onCheckIsTextEditor()) {
+                controlFlags |= CONTROL_WINDOW_IS_TEXT_EDITOR;
             }
         }
-
-        if (startInput) {
-            startInputInner();
+        if (first) {
+            controlFlags |= CONTROL_WINDOW_FIRST;
+        }
+        
+        if (checkFocusNoStartInput(forceNewFocus)) {
+            // We need to restart input on the current focus view.  This
+            // should be done in conjunction with telling the system service
+            // about the window gaining focus, to help make the transition
+            // smooth.
+            if (startInputInner(rootView.getWindowToken(),
+                    controlFlags, softInputMode, windowFlags)) {
+                return;
+            }
+        }
+        
+        // For some reason we didn't do a startInput + windowFocusGain, so
+        // we'll just do a window focus gain and call it a day.
+        synchronized (mH) {
+            try {
+                mService.windowGainedFocus(mClient, rootView.getWindowToken(),
+                        controlFlags, softInputMode, windowFlags, null, null);
+            } catch (RemoteException e) {
+            }
         }
     }
     
@@ -1649,8 +1695,7 @@
         p.println("  mCurMethod=" + mCurMethod);
         p.println("  mCurRootView=" + mCurRootView);
         p.println("  mServedView=" + mServedView);
-        p.println("  mNextServedNeedsStart=" + mNextServedNeedsStart
-                + " mNextServedView=" + mNextServedView);
+        p.println("  mNextServedView=" + mNextServedView);
         p.println("  mServedConnecting=" + mServedConnecting);
         if (mCurrentTextBoxAttribute != null) {
             p.println("  mCurrentTextBoxAttribute:");
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 683aca5..714d8ba 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -43,16 +43,17 @@
     void removeClient(in IInputMethodClient client);
             
     InputBindResult startInput(in IInputMethodClient client,
-            IInputContext inputContext, in EditorInfo attribute,
-            boolean initial, boolean needResult);
+            IInputContext inputContext, in EditorInfo attribute, int controlFlags);
     void finishInput(in IInputMethodClient client);
     boolean showSoftInput(in IInputMethodClient client, int flags,
             in ResultReceiver resultReceiver);
     boolean hideSoftInput(in IInputMethodClient client, int flags,
             in ResultReceiver resultReceiver);
-    void windowGainedFocus(in IInputMethodClient client, in IBinder windowToken,
-            boolean viewHasFocus, boolean isTextEditor,
-            int softInputMode, boolean first, int windowFlags);
+    // Report that a window has gained focus.  If 'attribute' is non-null,
+    // this will also do a startInput.
+    InputBindResult windowGainedFocus(in IInputMethodClient client, in IBinder windowToken,
+            int controlFlags, int softInputMode, int windowFlags,
+            in EditorInfo attribute, IInputContext inputContext);
             
     void showInputMethodPickerFromClient(in IInputMethodClient client);
     void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId);
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index f5c4ed4..7d4faea 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -780,7 +780,7 @@
         return flags;
     }
 
-    InputBindResult attachNewInputLocked(boolean initial, boolean needResult) {
+    InputBindResult attachNewInputLocked(boolean initial) {
         if (!mBoundToMethod) {
             executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
                     MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
@@ -798,14 +798,11 @@
             if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
             showCurrentInputLocked(getAppShowFlags(), null);
         }
-        return needResult
-                ? new InputBindResult(session.session, mCurId, mCurSeq)
-                : null;
+        return new InputBindResult(session.session, mCurId, mCurSeq);
     }
 
     InputBindResult startInputLocked(IInputMethodClient client,
-            IInputContext inputContext, EditorInfo attribute,
-            boolean initial, boolean needResult) {
+            IInputContext inputContext, EditorInfo attribute, int controlFlags) {
         // If no method is currently selected, do nothing.
         if (mCurMethodId == null) {
             return mNoBinding;
@@ -831,6 +828,16 @@
         } catch (RemoteException e) {
         }
 
+        return startInputUncheckedLocked(cs, inputContext, attribute, controlFlags);
+    }
+
+    InputBindResult startInputUncheckedLocked(ClientState cs,
+            IInputContext inputContext, EditorInfo attribute, int controlFlags) {
+        // If no method is currently selected, do nothing.
+        if (mCurMethodId == null) {
+            return mNoBinding;
+        }
+
         if (mCurClient != cs) {
             // If the client is changing, we need to switch over to the new
             // one.
@@ -861,7 +868,8 @@
             if (cs.curSession != null) {
                 // Fast case: if we are already connected to the input method,
                 // then just return it.
-                return attachNewInputLocked(initial, needResult);
+                return attachNewInputLocked(
+                        (controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0);
             }
             if (mHaveConnection) {
                 if (mCurMethod != null) {
@@ -942,13 +950,11 @@
 
     @Override
     public InputBindResult startInput(IInputMethodClient client,
-            IInputContext inputContext, EditorInfo attribute,
-            boolean initial, boolean needResult) {
+            IInputContext inputContext, EditorInfo attribute, int controlFlags) {
         synchronized (mMethodMap) {
             final long ident = Binder.clearCallingIdentity();
             try {
-                return startInputLocked(client, inputContext, attribute,
-                        initial, needResult);
+                return startInputLocked(client, inputContext, attribute, controlFlags);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -991,7 +997,7 @@
                     mCurClient.curSession = new SessionState(mCurClient,
                             method, session);
                     mCurClient.sessionRequested = false;
-                    InputBindResult res = attachNewInputLocked(true, true);
+                    InputBindResult res = attachNewInputLocked(true);
                     if (res.method != null) {
                         executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
                                 MSG_BIND_METHOD, mCurClient.client, res));
@@ -1476,36 +1482,45 @@
     }
 
     @Override
-    public void windowGainedFocus(IInputMethodClient client, IBinder windowToken,
-            boolean viewHasFocus, boolean isTextEditor, int softInputMode,
-            boolean first, int windowFlags) {
+    public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken,
+            int controlFlags, int softInputMode, int windowFlags,
+            EditorInfo attribute, IInputContext inputContext) {
+        InputBindResult res = null;
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mMethodMap) {
                 if (DEBUG) Slog.v(TAG, "windowGainedFocus: " + client.asBinder()
-                        + " viewHasFocus=" + viewHasFocus
-                        + " isTextEditor=" + isTextEditor
+                        + " controlFlags=#" + Integer.toHexString(controlFlags)
                         + " softInputMode=#" + Integer.toHexString(softInputMode)
-                        + " first=" + first + " flags=#"
-                        + Integer.toHexString(windowFlags));
+                        + " windowFlags=#" + Integer.toHexString(windowFlags));
 
-                if (mCurClient == null || client == null
-                        || mCurClient.client.asBinder() != client.asBinder()) {
-                    try {
-                        // We need to check if this is the current client with
-                        // focus in the window manager, to allow this call to
-                        // be made before input is started in it.
-                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
-                            Slog.w(TAG, "Client not active, ignoring focus gain of: " + client);
-                            return;
-                        }
-                    } catch (RemoteException e) {
+                ClientState cs = mClients.get(client.asBinder());
+                if (cs == null) {
+                    throw new IllegalArgumentException("unknown client "
+                            + client.asBinder());
+                }
+
+                try {
+                    if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
+                        // Check with the window manager to make sure this client actually
+                        // has a window with focus.  If not, reject.  This is thread safe
+                        // because if the focus changes some time before or after, the
+                        // next client receiving focus that has any interest in input will
+                        // be calling through here after that change happens.
+                        Slog.w(TAG, "Focus gain on non-focused client " + cs.client
+                                + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
+                        return null;
                     }
+                } catch (RemoteException e) {
                 }
 
                 if (mCurFocusedWindow == windowToken) {
                     Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client);
-                    return;
+                    if (attribute != null) {
+                        return startInputUncheckedLocked(cs, inputContext, attribute,
+                                controlFlags);
+                    }
+                    return null;
                 }
                 mCurFocusedWindow = windowToken;
 
@@ -1521,6 +1536,14 @@
                                 == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
                         || mRes.getConfiguration().isLayoutSizeAtLeast(
                                 Configuration.SCREENLAYOUT_SIZE_LARGE);
+                final boolean isTextEditor =
+                        (controlFlags&InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0;
+
+                // We want to start input before showing the IME, but after closing
+                // it.  We want to do this after closing it to help the IME disappear
+                // more quickly (not get stuck behind it initializing itself for the
+                // new focused input, even if its window wants to hide the IME).
+                boolean didStart = false;
                         
                 switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
@@ -1536,12 +1559,17 @@
                                 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
                             // There is a focus view, and we are navigating forward
                             // into the window, so show the input window for the user.
-                            // We only do this automatically if the window an resize
-                            // to accomodate the IME (so what the user sees will give
+                            // We only do this automatically if the window can resize
+                            // to accommodate the IME (so what the user sees will give
                             // them good context without input information being obscured
                             // by the IME) or if running on a large screen where there
                             // is more room for the target window + IME.
                             if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
+                            if (attribute != null) {
+                                res = startInputUncheckedLocked(cs, inputContext, attribute,
+                                        controlFlags);
+                                didStart = true;
+                            }
                             showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
                         }
                         break;
@@ -1563,18 +1591,35 @@
                         if ((softInputMode &
                                 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
                             if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
+                            if (attribute != null) {
+                                res = startInputUncheckedLocked(cs, inputContext, attribute,
+                                        controlFlags);
+                                didStart = true;
+                            }
                             showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
                         }
                         break;
                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
                         if (DEBUG) Slog.v(TAG, "Window asks to always show input");
+                        if (attribute != null) {
+                            res = startInputUncheckedLocked(cs, inputContext, attribute,
+                                    controlFlags);
+                            didStart = true;
+                        }
                         showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
                         break;
                 }
+
+                if (!didStart && attribute != null) {
+                    res = startInputUncheckedLocked(cs, inputContext, attribute,
+                            controlFlags);
+                }
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
+
+        return res;
     }
 
     @Override