From 7663d80f6b6fd6ca7a736c3802013a09c0abdeb9 Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Fri, 24 Feb 2012 13:08:49 -0800 Subject: 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 --- .../view/inputmethod/InputMethodManager.java | 131 ++++++++++++++------- .../android/internal/view/IInputMethodManager.aidl | 11 +- .../android/server/InputMethodManagerService.java | 111 +++++++++++------ 3 files changed, 172 insertions(+), 81 deletions(-) diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 925e78129dbe..ce36046a26b5 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.IInputMethodSession; 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 @@ public final class InputMethodManager { 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 @@ public final class InputMethodManager { /** * 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; @@ -242,11 +265,6 @@ public final class InputMethodManager { * we get around to updating things. */ 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 @@ public final class InputMethodManager { mCurId = res.id; mBindSequence = res.sequence; } - startInputInner(); + startInputInner(null, 0, 0, 0); return; } case MSG_UNBIND: { @@ -362,7 +380,7 @@ public final class InputMethodManager { } } if (startInput) { - startInputInner(); + startInputInner(null, 0, 0, 0); } return; } @@ -952,10 +970,11 @@ public final class InputMethodManager { 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 @@ public final class InputMethodManager { 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 @@ public final class InputMethodManager { // 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 @@ public final class InputMethodManager { 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 @@ public final class InputMethodManager { 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 @@ public final class InputMethodManager { 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 @@ public final class InputMethodManager { } 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 @@ public final class InputMethodManager { Log.w(TAG, "IME died: " + mCurId, e); } } + + return true; } /** @@ -1133,27 +1164,26 @@ public final class InputMethodManager { * @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 @@ public final class InputMethodManager { } 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,27 +1230,42 @@ public final class InputMethodManager { if (mHasBeenInactive) { if (DEBUG) Log.v(TAG, "Has been inactive! Starting fresh"); mHasBeenInactive = false; - mNextServedNeedsStart = true; + forceNewFocus = true; } focusInLocked(focusedView != null ? focusedView : rootView); } + + int controlFlags = 0; + if (focusedView != null) { + controlFlags |= CONTROL_WINDOW_VIEW_HAS_FOCUS; + if (focusedView.onCheckIsTextEditor()) { + controlFlags |= CONTROL_WINDOW_IS_TEXT_EDITOR; + } + } + if (first) { + controlFlags |= CONTROL_WINDOW_FIRST; + } - boolean startInput = checkFocusNoStartInput(); + 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 { - final boolean isTextEditor = focusedView != null && - focusedView.onCheckIsTextEditor(); mService.windowGainedFocus(mClient, rootView.getWindowToken(), - focusedView != null, isTextEditor, softInputMode, first, - windowFlags); + controlFlags, softInputMode, windowFlags, null, null); } catch (RemoteException e) { } } - - if (startInput) { - startInputInner(); - } } /** @hide */ @@ -1649,8 +1695,7 @@ public final class InputMethodManager { 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 683aca542ba8..714d8ba75748 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -43,16 +43,17 @@ interface IInputMethodManager { 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 f5c4ed46c411..7d4faea20388 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -780,7 +780,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub 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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub 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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } 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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub 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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub 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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub == 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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub 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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub 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 -- cgit v1.2.3-59-g8ed1b