Loading core/java/android/inputmethodservice/InputMethodService.java +11 −46 Original line number Diff line number Diff line Loading @@ -62,7 +62,6 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowManager; import android.view.WindowManager.BadTokenException; import android.view.animation.AnimationUtils; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CursorAnchorInfo; Loading Loading @@ -354,7 +353,6 @@ public class InputMethodService extends AbstractInputMethodService { SoftInputWindow mWindow; boolean mInitialized; boolean mWindowCreated; boolean mWindowAdded; boolean mWindowVisible; boolean mWindowWasVisible; boolean mInShowWindow; Loading Loading @@ -572,16 +570,7 @@ public class InputMethodService extends AbstractInputMethodService { if (DEBUG) Log.v(TAG, "showSoftInput()"); boolean wasVis = isInputViewShown(); if (dispatchOnShowInputRequested(flags, false)) { try { showWindow(true); } catch (BadTokenException e) { // We have ignored BadTokenException here since Jelly Bean MR-2 (API Level 18). // We could ignore BadTokenException in InputMethodService#showWindow() instead, // but it may break assumptions for those who override #showWindow() that we can // detect errors in #showWindow() by checking BadTokenException. // TODO: Investigate its feasibility. Update JavaDoc of #showWindow() of // whether it's OK to override #showWindow() or not. } } clearInsetOfPreviousIme(); // If user uses hard keyboard, IME button should always be shown. Loading Loading @@ -999,13 +988,7 @@ public class InputMethodService extends AbstractInputMethodService { mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener( mInsetsComputer); doFinishInput(); if (mWindowAdded) { // Disable exit animation for the current IME window // to avoid the race condition between the exit and enter animations // when the current IME is being switched to another one. mWindow.getWindow().setWindowAnimations(0); mWindow.dismiss(); } mWindow.dismissForDestroyIfNecessary(); if (mSettingsObserver != null) { mSettingsObserver.unregister(); mSettingsObserver = null; Loading Loading @@ -1792,7 +1775,6 @@ public class InputMethodService extends AbstractInputMethodService { public void showWindow(boolean showInput) { if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput + " mShowInputRequested=" + mShowInputRequested + " mWindowAdded=" + mWindowAdded + " mWindowCreated=" + mWindowCreated + " mWindowVisible=" + mWindowVisible + " mInputStarted=" + mInputStarted Loading @@ -1803,27 +1785,12 @@ public class InputMethodService extends AbstractInputMethodService { return; } try { mWindowWasVisible = mWindowVisible; mInShowWindow = true; showWindowInner(showInput); } catch (BadTokenException e) { // BadTokenException is a normal consequence in certain situations, e.g., swapping IMEs // while there is a DO_SHOW_SOFT_INPUT message in the IIMethodWrapper queue. if (DEBUG) Log.v(TAG, "BadTokenException: IME is done."); mWindowVisible = false; mWindowAdded = false; // Rethrow the exception to preserve the existing behavior. Some IMEs may have directly // called this method and relied on this exception for some clean-up tasks. // TODO: Give developers a clear guideline of whether it's OK to call this method or // InputMethodService#requestShowSelf(int) should always be used instead. throw e; } finally { // TODO: Is it OK to set true when we get BadTokenException? mWindowWasVisible = true; mInShowWindow = false; } } void showWindowInner(boolean showInput) { boolean doShowInput = false; Loading @@ -1840,8 +1807,7 @@ public class InputMethodService extends AbstractInputMethodService { updateFullscreenMode(); updateInputViewShown(); if (!mWindowAdded || !mWindowCreated) { mWindowAdded = true; if (!mWindowCreated) { mWindowCreated = true; initialize(); if (DEBUG) Log.v(TAG, "CALL: onCreateCandidatesView"); Loading Loading @@ -2867,8 +2833,7 @@ public class InputMethodService extends AbstractInputMethodService { @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { final Printer p = new PrintWriterPrinter(fout); p.println("Input method service state for " + this + ":"); p.println(" mWindowCreated=" + mWindowCreated + " mWindowAdded=" + mWindowAdded); p.println(" mWindowCreated=" + mWindowCreated); p.println(" mWindowVisible=" + mWindowVisible + " mWindowWasVisible=" + mWindowWasVisible + " mInShowWindow=" + mInShowWindow); Loading core/java/android/inputmethodservice/SoftInputWindow.java +168 −4 Original line number Diff line number Diff line Loading @@ -16,15 +16,22 @@ package android.inputmethodservice; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; import android.app.Dialog; import android.content.Context; import android.graphics.Rect; import android.os.Debug; import android.os.IBinder; import android.util.Log; import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.WindowManager; import java.lang.annotation.Retention; /** * A SoftInputWindow is a Dialog that is intended to be used for a top-level input * method window. It will be displayed along the edge of the screen, moving Loading @@ -33,6 +40,9 @@ import android.view.WindowManager; * @hide */ public class SoftInputWindow extends Dialog { private static final boolean DEBUG = false; private static final String TAG = "SoftInputWindow"; final String mName; final Callback mCallback; final KeyEvent.Callback mKeyEventCallback; Loading @@ -42,14 +52,63 @@ public class SoftInputWindow extends Dialog { final boolean mTakesFocus; private final Rect mBounds = new Rect(); @Retention(SOURCE) @IntDef(value = {SoftInputWindowState.TOKEN_PENDING, SoftInputWindowState.TOKEN_SET, SoftInputWindowState.SHOWN_AT_LEAST_ONCE, SoftInputWindowState.REJECTED_AT_LEAST_ONCE}) private @interface SoftInputWindowState { /** * The window token is not set yet. */ int TOKEN_PENDING = 0; /** * The window token was set, but the window is not shown yet. */ int TOKEN_SET = 1; /** * The window was shown at least once. */ int SHOWN_AT_LEAST_ONCE = 2; /** * {@link android.view.WindowManager.BadTokenException} was sent when calling * {@link Dialog#show()} at least once. */ int REJECTED_AT_LEAST_ONCE = 3; /** * The window is considered destroyed. Any incoming request should be ignored. */ int DESTROYED = 4; } @SoftInputWindowState private int mWindowState = SoftInputWindowState.TOKEN_PENDING; public interface Callback { public void onBackPressed(); } public void setToken(IBinder token) { switch (mWindowState) { case SoftInputWindowState.TOKEN_PENDING: // Normal scenario. Nothing to worry about. WindowManager.LayoutParams lp = getWindow().getAttributes(); lp.token = token; getWindow().setAttributes(lp); updateWindowState(SoftInputWindowState.TOKEN_SET); return; case SoftInputWindowState.TOKEN_SET: case SoftInputWindowState.SHOWN_AT_LEAST_ONCE: case SoftInputWindowState.REJECTED_AT_LEAST_ONCE: throw new IllegalStateException("setToken can be called only once"); case SoftInputWindowState.DESTROYED: // Just ignore. Since there are multiple event queues from the token is issued // in the system server to the timing when it arrives here, it can be delivered // after the is already destroyed. No one should be blamed because of such an // unfortunate but possible scenario. Log.i(TAG, "Ignoring setToken() because window is already destroyed."); return; default: throw new IllegalStateException("Unexpected state=" + mWindowState); } } /** Loading Loading @@ -190,4 +249,109 @@ public class SoftInputWindow extends Dialog { getWindow().setFlags(windowSetFlags, windowModFlags); } @Override public final void show() { switch (mWindowState) { case SoftInputWindowState.TOKEN_PENDING: throw new IllegalStateException("Window token is not set yet."); case SoftInputWindowState.TOKEN_SET: case SoftInputWindowState.SHOWN_AT_LEAST_ONCE: // Normal scenario. Nothing to worry about. try { super.show(); updateWindowState(SoftInputWindowState.SHOWN_AT_LEAST_ONCE); } catch (WindowManager.BadTokenException e) { // Just ignore this exception. Since show() can be requested from other // components such as the system and there could be multiple event queues before // the request finally arrives here, the system may have already invalidated the // window token attached to our window. In such a scenario, receiving // BadTokenException here is an expected behavior. We just ignore it and update // the state so that we do not touch this window later. Log.i(TAG, "Probably the IME window token is already invalidated." + " show() does nothing."); updateWindowState(SoftInputWindowState.REJECTED_AT_LEAST_ONCE); } return; case SoftInputWindowState.REJECTED_AT_LEAST_ONCE: // Just ignore. In general we cannot completely avoid this kind of race condition. Log.i(TAG, "Not trying to call show() because it was already rejected once."); return; case SoftInputWindowState.DESTROYED: // Just ignore. In general we cannot completely avoid this kind of race condition. Log.i(TAG, "Ignoring show() because the window is already destroyed."); return; default: throw new IllegalStateException("Unexpected state=" + mWindowState); } } final void dismissForDestroyIfNecessary() { switch (mWindowState) { case SoftInputWindowState.TOKEN_PENDING: case SoftInputWindowState.TOKEN_SET: // nothing to do because the window has never been shown. updateWindowState(SoftInputWindowState.DESTROYED); return; case SoftInputWindowState.SHOWN_AT_LEAST_ONCE: // Disable exit animation for the current IME window // to avoid the race condition between the exit and enter animations // when the current IME is being switched to another one. try { getWindow().setWindowAnimations(0); dismiss(); } catch (WindowManager.BadTokenException e) { // Just ignore this exception. Since show() can be requested from other // components such as the system and there could be multiple event queues before // the request finally arrives here, the system may have already invalidated the // window token attached to our window. In such a scenario, receiving // BadTokenException here is an expected behavior. We just ignore it and update // the state so that we do not touch this window later. Log.i(TAG, "Probably the IME window token is already invalidated. " + "No need to dismiss it."); } // Either way, consider that the window is destroyed. updateWindowState(SoftInputWindowState.DESTROYED); return; case SoftInputWindowState.REJECTED_AT_LEAST_ONCE: // Just ignore. In general we cannot completely avoid this kind of race condition. Log.i(TAG, "Not trying to dismiss the window because it is most likely unnecessary."); // Anyway, consider that the window is destroyed. updateWindowState(SoftInputWindowState.DESTROYED); return; case SoftInputWindowState.DESTROYED: throw new IllegalStateException( "dismissForDestroyIfNecessary can be called only once"); default: throw new IllegalStateException("Unexpected state=" + mWindowState); } } private void updateWindowState(@SoftInputWindowState int newState) { if (DEBUG) { if (mWindowState != newState) { Log.d(TAG, "WindowState: " + stateToString(mWindowState) + " -> " + stateToString(newState) + " @ " + Debug.getCaller()); } } mWindowState = newState; } private static String stateToString(@SoftInputWindowState int state) { switch (state) { case SoftInputWindowState.TOKEN_PENDING: return "TOKEN_PENDING"; case SoftInputWindowState.TOKEN_SET: return "TOKEN_SET"; case SoftInputWindowState.SHOWN_AT_LEAST_ONCE: return "SHOWN_AT_LEAST_ONCE"; case SoftInputWindowState.REJECTED_AT_LEAST_ONCE: return "REJECTED_AT_LEAST_ONCE"; case SoftInputWindowState.DESTROYED: return "DESTROYED"; default: throw new IllegalStateException("Unknown state=" + state); } } } Loading
core/java/android/inputmethodservice/InputMethodService.java +11 −46 Original line number Diff line number Diff line Loading @@ -62,7 +62,6 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowManager; import android.view.WindowManager.BadTokenException; import android.view.animation.AnimationUtils; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CursorAnchorInfo; Loading Loading @@ -354,7 +353,6 @@ public class InputMethodService extends AbstractInputMethodService { SoftInputWindow mWindow; boolean mInitialized; boolean mWindowCreated; boolean mWindowAdded; boolean mWindowVisible; boolean mWindowWasVisible; boolean mInShowWindow; Loading Loading @@ -572,16 +570,7 @@ public class InputMethodService extends AbstractInputMethodService { if (DEBUG) Log.v(TAG, "showSoftInput()"); boolean wasVis = isInputViewShown(); if (dispatchOnShowInputRequested(flags, false)) { try { showWindow(true); } catch (BadTokenException e) { // We have ignored BadTokenException here since Jelly Bean MR-2 (API Level 18). // We could ignore BadTokenException in InputMethodService#showWindow() instead, // but it may break assumptions for those who override #showWindow() that we can // detect errors in #showWindow() by checking BadTokenException. // TODO: Investigate its feasibility. Update JavaDoc of #showWindow() of // whether it's OK to override #showWindow() or not. } } clearInsetOfPreviousIme(); // If user uses hard keyboard, IME button should always be shown. Loading Loading @@ -999,13 +988,7 @@ public class InputMethodService extends AbstractInputMethodService { mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener( mInsetsComputer); doFinishInput(); if (mWindowAdded) { // Disable exit animation for the current IME window // to avoid the race condition between the exit and enter animations // when the current IME is being switched to another one. mWindow.getWindow().setWindowAnimations(0); mWindow.dismiss(); } mWindow.dismissForDestroyIfNecessary(); if (mSettingsObserver != null) { mSettingsObserver.unregister(); mSettingsObserver = null; Loading Loading @@ -1792,7 +1775,6 @@ public class InputMethodService extends AbstractInputMethodService { public void showWindow(boolean showInput) { if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput + " mShowInputRequested=" + mShowInputRequested + " mWindowAdded=" + mWindowAdded + " mWindowCreated=" + mWindowCreated + " mWindowVisible=" + mWindowVisible + " mInputStarted=" + mInputStarted Loading @@ -1803,27 +1785,12 @@ public class InputMethodService extends AbstractInputMethodService { return; } try { mWindowWasVisible = mWindowVisible; mInShowWindow = true; showWindowInner(showInput); } catch (BadTokenException e) { // BadTokenException is a normal consequence in certain situations, e.g., swapping IMEs // while there is a DO_SHOW_SOFT_INPUT message in the IIMethodWrapper queue. if (DEBUG) Log.v(TAG, "BadTokenException: IME is done."); mWindowVisible = false; mWindowAdded = false; // Rethrow the exception to preserve the existing behavior. Some IMEs may have directly // called this method and relied on this exception for some clean-up tasks. // TODO: Give developers a clear guideline of whether it's OK to call this method or // InputMethodService#requestShowSelf(int) should always be used instead. throw e; } finally { // TODO: Is it OK to set true when we get BadTokenException? mWindowWasVisible = true; mInShowWindow = false; } } void showWindowInner(boolean showInput) { boolean doShowInput = false; Loading @@ -1840,8 +1807,7 @@ public class InputMethodService extends AbstractInputMethodService { updateFullscreenMode(); updateInputViewShown(); if (!mWindowAdded || !mWindowCreated) { mWindowAdded = true; if (!mWindowCreated) { mWindowCreated = true; initialize(); if (DEBUG) Log.v(TAG, "CALL: onCreateCandidatesView"); Loading Loading @@ -2867,8 +2833,7 @@ public class InputMethodService extends AbstractInputMethodService { @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { final Printer p = new PrintWriterPrinter(fout); p.println("Input method service state for " + this + ":"); p.println(" mWindowCreated=" + mWindowCreated + " mWindowAdded=" + mWindowAdded); p.println(" mWindowCreated=" + mWindowCreated); p.println(" mWindowVisible=" + mWindowVisible + " mWindowWasVisible=" + mWindowWasVisible + " mInShowWindow=" + mInShowWindow); Loading
core/java/android/inputmethodservice/SoftInputWindow.java +168 −4 Original line number Diff line number Diff line Loading @@ -16,15 +16,22 @@ package android.inputmethodservice; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; import android.app.Dialog; import android.content.Context; import android.graphics.Rect; import android.os.Debug; import android.os.IBinder; import android.util.Log; import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.WindowManager; import java.lang.annotation.Retention; /** * A SoftInputWindow is a Dialog that is intended to be used for a top-level input * method window. It will be displayed along the edge of the screen, moving Loading @@ -33,6 +40,9 @@ import android.view.WindowManager; * @hide */ public class SoftInputWindow extends Dialog { private static final boolean DEBUG = false; private static final String TAG = "SoftInputWindow"; final String mName; final Callback mCallback; final KeyEvent.Callback mKeyEventCallback; Loading @@ -42,14 +52,63 @@ public class SoftInputWindow extends Dialog { final boolean mTakesFocus; private final Rect mBounds = new Rect(); @Retention(SOURCE) @IntDef(value = {SoftInputWindowState.TOKEN_PENDING, SoftInputWindowState.TOKEN_SET, SoftInputWindowState.SHOWN_AT_LEAST_ONCE, SoftInputWindowState.REJECTED_AT_LEAST_ONCE}) private @interface SoftInputWindowState { /** * The window token is not set yet. */ int TOKEN_PENDING = 0; /** * The window token was set, but the window is not shown yet. */ int TOKEN_SET = 1; /** * The window was shown at least once. */ int SHOWN_AT_LEAST_ONCE = 2; /** * {@link android.view.WindowManager.BadTokenException} was sent when calling * {@link Dialog#show()} at least once. */ int REJECTED_AT_LEAST_ONCE = 3; /** * The window is considered destroyed. Any incoming request should be ignored. */ int DESTROYED = 4; } @SoftInputWindowState private int mWindowState = SoftInputWindowState.TOKEN_PENDING; public interface Callback { public void onBackPressed(); } public void setToken(IBinder token) { switch (mWindowState) { case SoftInputWindowState.TOKEN_PENDING: // Normal scenario. Nothing to worry about. WindowManager.LayoutParams lp = getWindow().getAttributes(); lp.token = token; getWindow().setAttributes(lp); updateWindowState(SoftInputWindowState.TOKEN_SET); return; case SoftInputWindowState.TOKEN_SET: case SoftInputWindowState.SHOWN_AT_LEAST_ONCE: case SoftInputWindowState.REJECTED_AT_LEAST_ONCE: throw new IllegalStateException("setToken can be called only once"); case SoftInputWindowState.DESTROYED: // Just ignore. Since there are multiple event queues from the token is issued // in the system server to the timing when it arrives here, it can be delivered // after the is already destroyed. No one should be blamed because of such an // unfortunate but possible scenario. Log.i(TAG, "Ignoring setToken() because window is already destroyed."); return; default: throw new IllegalStateException("Unexpected state=" + mWindowState); } } /** Loading Loading @@ -190,4 +249,109 @@ public class SoftInputWindow extends Dialog { getWindow().setFlags(windowSetFlags, windowModFlags); } @Override public final void show() { switch (mWindowState) { case SoftInputWindowState.TOKEN_PENDING: throw new IllegalStateException("Window token is not set yet."); case SoftInputWindowState.TOKEN_SET: case SoftInputWindowState.SHOWN_AT_LEAST_ONCE: // Normal scenario. Nothing to worry about. try { super.show(); updateWindowState(SoftInputWindowState.SHOWN_AT_LEAST_ONCE); } catch (WindowManager.BadTokenException e) { // Just ignore this exception. Since show() can be requested from other // components such as the system and there could be multiple event queues before // the request finally arrives here, the system may have already invalidated the // window token attached to our window. In such a scenario, receiving // BadTokenException here is an expected behavior. We just ignore it and update // the state so that we do not touch this window later. Log.i(TAG, "Probably the IME window token is already invalidated." + " show() does nothing."); updateWindowState(SoftInputWindowState.REJECTED_AT_LEAST_ONCE); } return; case SoftInputWindowState.REJECTED_AT_LEAST_ONCE: // Just ignore. In general we cannot completely avoid this kind of race condition. Log.i(TAG, "Not trying to call show() because it was already rejected once."); return; case SoftInputWindowState.DESTROYED: // Just ignore. In general we cannot completely avoid this kind of race condition. Log.i(TAG, "Ignoring show() because the window is already destroyed."); return; default: throw new IllegalStateException("Unexpected state=" + mWindowState); } } final void dismissForDestroyIfNecessary() { switch (mWindowState) { case SoftInputWindowState.TOKEN_PENDING: case SoftInputWindowState.TOKEN_SET: // nothing to do because the window has never been shown. updateWindowState(SoftInputWindowState.DESTROYED); return; case SoftInputWindowState.SHOWN_AT_LEAST_ONCE: // Disable exit animation for the current IME window // to avoid the race condition between the exit and enter animations // when the current IME is being switched to another one. try { getWindow().setWindowAnimations(0); dismiss(); } catch (WindowManager.BadTokenException e) { // Just ignore this exception. Since show() can be requested from other // components such as the system and there could be multiple event queues before // the request finally arrives here, the system may have already invalidated the // window token attached to our window. In such a scenario, receiving // BadTokenException here is an expected behavior. We just ignore it and update // the state so that we do not touch this window later. Log.i(TAG, "Probably the IME window token is already invalidated. " + "No need to dismiss it."); } // Either way, consider that the window is destroyed. updateWindowState(SoftInputWindowState.DESTROYED); return; case SoftInputWindowState.REJECTED_AT_LEAST_ONCE: // Just ignore. In general we cannot completely avoid this kind of race condition. Log.i(TAG, "Not trying to dismiss the window because it is most likely unnecessary."); // Anyway, consider that the window is destroyed. updateWindowState(SoftInputWindowState.DESTROYED); return; case SoftInputWindowState.DESTROYED: throw new IllegalStateException( "dismissForDestroyIfNecessary can be called only once"); default: throw new IllegalStateException("Unexpected state=" + mWindowState); } } private void updateWindowState(@SoftInputWindowState int newState) { if (DEBUG) { if (mWindowState != newState) { Log.d(TAG, "WindowState: " + stateToString(mWindowState) + " -> " + stateToString(newState) + " @ " + Debug.getCaller()); } } mWindowState = newState; } private static String stateToString(@SoftInputWindowState int state) { switch (state) { case SoftInputWindowState.TOKEN_PENDING: return "TOKEN_PENDING"; case SoftInputWindowState.TOKEN_SET: return "TOKEN_SET"; case SoftInputWindowState.SHOWN_AT_LEAST_ONCE: return "SHOWN_AT_LEAST_ONCE"; case SoftInputWindowState.REJECTED_AT_LEAST_ONCE: return "REJECTED_AT_LEAST_ONCE"; case SoftInputWindowState.DESTROYED: return "DESTROYED"; default: throw new IllegalStateException("Unknown state=" + state); } } }