--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoEditable.java
@@ -1204,32 +1204,47 @@ import android.view.inputmethod.EditorIn
if (mNeedSync) {
icSyncShadowText();
}
return;
}
switch (type) {
case SessionTextInput.EditableListener.NOTIFY_IME_OF_FOCUS:
+ if (mFocusedChild != null) {
+ // Already focused, so blur first.
+ icRestartInput(GeckoSession.TextInputDelegate.RESTART_REASON_BLUR,
+ /* toggleSoftInput */ false);
+ }
+
mFocusedChild = child;
mNeedSync = false;
mText.syncShadowText(/* listener */ null);
- // Do not reset mIMEState here; see comments in notifyIMEContext
- if (mIMEState != SessionTextInput.EditableListener.IME_STATE_DISABLED) {
- icRestartInput(GeckoSession.TextInputDelegate.RESTART_REASON_FOCUS);
+ // Most of the time notifyIMEContext comes _before_ notifyIME, but sometimes it
+ // comes _after_ notifyIME. In that case, the state is disabled here, and
+ // notifyIMEContext is responsible for calling restartInput.
+ if (mIMEState == SessionTextInput.EditableListener.IME_STATE_DISABLED) {
+ mIMEState = SessionTextInput.EditableListener.IME_STATE_UNKNOWN;
+ } else {
+ icRestartInput(GeckoSession.TextInputDelegate.RESTART_REASON_FOCUS,
+ /* toggleSoftInput */ true);
}
break;
case SessionTextInput.EditableListener.NOTIFY_IME_OF_BLUR:
- mFocusedChild = null;
+ if (mFocusedChild != null) {
+ mFocusedChild = null;
+ icRestartInput(GeckoSession.TextInputDelegate.RESTART_REASON_BLUR,
+ /* toggleSoftInput */ true);
+ }
break;
case SessionTextInput.EditableListener.NOTIFY_IME_OPEN_VKB:
- toggleSoftInput(/* force */ true);
+ toggleSoftInput(/* force */ true, mIMEState);
return; // Don't notify listener.
case SessionTextInput.EditableListener.NOTIFY_IME_TO_COMMIT_COMPOSITION: {
// Gecko already committed its composition. However, Android keyboards
// have trouble dealing with us removing the composition manually on the
// Java side. Therefore, we keep the composition intact on the Java side.
// The text content should still be in-sync on both sides.
//
@@ -1239,17 +1254,18 @@ import android.view.inputmethod.EditorIn
final Object[] spans = text.getSpans(0, text.length(), Object.class);
for (final Object span : spans) {
if ((text.getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) {
// Still have composition; no need to reset.
return; // Don't notify listener.
}
}
// No longer have composition; perform reset.
- icRestartInput(GeckoSession.TextInputDelegate.RESTART_REASON_CONTENT_CHANGE);
+ icRestartInput(GeckoSession.TextInputDelegate.RESTART_REASON_CONTENT_CHANGE,
+ /* toggleSoftInput */ false);
return; // Don't notify listener.
}
default:
throw new IllegalArgumentException("Invalid notifyIME type: " + type);
}
if (mListener != null) {
@@ -1306,48 +1322,65 @@ import android.view.inputmethod.EditorIn
mIMEModeHint = (modeHint == null) ? "" : modeHint;
mIMEActionHint = (actionHint == null) ? "" : actionHint;
mIMEFlags = flags;
if (mListener != null) {
mListener.notifyIMEContext(state, typeHint, modeHint, actionHint, flags);
}
- // On focus, the notifyIMEContext call comes *before* the
- // notifyIME(NOTIFY_IME_OF_FOCUS) call, but we need to call restartInput during
- // notifyIME, so we skip restartInput here. On blur, the notifyIMEContext call
- // comes *after* the notifyIME(NOTIFY_IME_OF_BLUR) call, and we need to call
- // restartInput here.
-
- // In either case, there is nothing to do here if we were disabled before.
- if (oldState == SessionTextInput.EditableListener.IME_STATE_DISABLED) {
+ if (state == SessionTextInput.EditableListener.IME_STATE_DISABLED ||
+ mFocusedChild == null) {
return;
}
- if (state == SessionTextInput.EditableListener.IME_STATE_DISABLED) {
- icRestartInput(GeckoSession.TextInputDelegate.RESTART_REASON_BLUR);
- } else if (mFocusedChild != null) {
- icRestartInput(GeckoSession.TextInputDelegate.RESTART_REASON_CONTENT_CHANGE);
+
+ // We changed state while focused. If the old state is unknown, it means this
+ // notifyIMEContext call came _after_ the notifyIME call, so we need to call
+ // restartInput(FOCUS) here (see comment in icNotifyIME). Otherwise, this change
+ // counts as a content change.
+ if (oldState == SessionTextInput.EditableListener.IME_STATE_UNKNOWN) {
+ icRestartInput(GeckoSession.TextInputDelegate.RESTART_REASON_FOCUS,
+ /* toggleSoftInput */ true);
+ } else if (oldState != SessionTextInput.EditableListener.IME_STATE_DISABLED) {
+ icRestartInput(GeckoSession.TextInputDelegate.RESTART_REASON_CONTENT_CHANGE,
+ /* toggleSoftInput */ false);
}
}
- private void icRestartInput(@GeckoSession.TextInputDelegate.RestartReason final int reason) {
+ private void icRestartInput(@GeckoSession.TextInputDelegate.RestartReason final int reason,
+ final boolean toggleSoftInput) {
if (DEBUG) {
assertOnIcThread();
}
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
- mSoftInputReentrancyGuard.incrementAndGet();
+ if (DEBUG) {
+ Log.d(LOGTAG, "restartInput(" + reason + ", " + toggleSoftInput + ')');
+ }
+ if (toggleSoftInput) {
+ mSoftInputReentrancyGuard.incrementAndGet();
+ }
mSession.getTextInput().getDelegate().restartInput(mSession, reason);
+ if (!toggleSoftInput) {
+ return;
+ }
postToInputConnection(new Runnable() {
@Override
public void run() {
- toggleSoftInput(/* force */ false);
+ int state = mIMEState;
+ if (reason == GeckoSession.TextInputDelegate.RESTART_REASON_BLUR &&
+ mFocusedChild == null) {
+ // On blur, notifyIMEContext() is called after notifyIME(). Therefore,
+ // mIMEState is not up-to-date here and we need to override it.
+ state = SessionTextInput.EditableListener.IME_STATE_DISABLED;
+ }
+ toggleSoftInput(/* force */ false, state);
}
});
}
});
}
public void onCreateInputConnection(final EditorInfo outAttrs) {
final int state = mIMEState;
@@ -1357,17 +1390,17 @@ import android.view.inputmethod.EditorIn
final int flags = mIMEFlags;
// Some keyboards require us to fill out outAttrs even if we return null.
outAttrs.imeOptions = EditorInfo.IME_ACTION_NONE;
outAttrs.actionLabel = null;
if (state == SessionTextInput.EditableListener.IME_STATE_DISABLED) {
outAttrs.inputType = InputType.TYPE_NULL;
- toggleSoftInput(/* force */ false);
+ toggleSoftInput(/* force */ false, state);
return;
}
outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
if (state == SessionTextInput.EditableListener.IME_STATE_PASSWORD ||
"password".equalsIgnoreCase(typeHint)) {
outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_PASSWORD;
} else if (typeHint.equalsIgnoreCase("url") ||
@@ -1426,22 +1459,24 @@ import android.view.inputmethod.EditorIn
Log.w(LOGTAG, "Unexpected actionHint=\"" + actionHint + "\"");
outAttrs.actionLabel = actionHint;
}
if ((flags & SessionTextInput.EditableListener.IME_FLAG_PRIVATE_BROWSING) != 0) {
outAttrs.imeOptions |= InputMethods.IME_FLAG_NO_PERSONALIZED_LEARNING;
}
- toggleSoftInput(/* force */ false);
+ toggleSoftInput(/* force */ false, state);
}
- /* package */ void toggleSoftInput(final boolean force) {
+ /* package */ void toggleSoftInput(final boolean force, final int state) {
+ if (DEBUG) {
+ Log.d(LOGTAG, "toggleSoftInput");
+ }
// Can be called from UI or IC thread.
- final int state = mIMEState;
final int flags = mIMEFlags;
// There are three paths that toggleSoftInput() can be called:
// 1) through calling restartInput(), which then indirectly calls
// onCreateInputConnection() and then toggleSoftInput().
// 2) through calling toggleSoftInput() directly from restartInput().
// This path is the fallback in case 1) does not happen.
// 3) through a system-generated onCreateInputConnection() call when the activity
@@ -1467,22 +1502,32 @@ import android.view.inputmethod.EditorIn
// the keyboard.
final View view = mSession.getTextInput().getView();
final boolean isFocused = (view == null) || view.hasFocus();
final boolean isUserAction = ((flags &
SessionTextInput.EditableListener.IME_FLAG_USER_ACTION) != 0);
if (!force && (isReentrant || !isFocused || !isUserAction)) {
+ if (DEBUG) {
+ Log.d(LOGTAG, "toggleSoftInput: no-op, reentrant=" + isReentrant +
+ ", focused=" + isFocused + ", user=" + isUserAction);
+ }
return;
}
if (state == SessionTextInput.EditableListener.IME_STATE_DISABLED) {
+ if (DEBUG) {
+ Log.d(LOGTAG, "hideSoftInput");
+ }
mSession.getTextInput().getDelegate().hideSoftInput(mSession);
return;
}
+ if (DEBUG) {
+ Log.d(LOGTAG, "showSoftInput");
+ }
mSession.getEventDispatcher().dispatch("GeckoView:ZoomToInput", null);
mSession.getTextInput().getDelegate().showSoftInput(mSession);
}
});
}
@Override // IGeckoEditableParent
public void onSelectionChange(final IBinder token,