--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditable.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditable.java
@@ -32,23 +32,26 @@ import android.text.SpannableStringBuild
import android.text.Spanned;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.style.CharacterStyle;
import android.util.Log;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
-/*
- GeckoEditable implements only some functions of Editable
- The field mText contains the actual underlying
- SpannableStringBuilder/Editable that contains our text.
-*/
-final class GeckoEditable extends IGeckoEditableParent.Stub
- implements InvocationHandler, Editable, GeckoEditableClient {
+/**
+ * GeckoEditable implements only some functions of Editable
+ * The field mText contains the actual underlying
+ * SpannableStringBuilder/Editable that contains our text.
+ */
+/* package */ final class GeckoEditable
+ extends IGeckoEditableParent.Stub
+ implements InvocationHandler,
+ Editable,
+ GeckoEditableClient {
private static final boolean DEBUG = false;
private static final String LOGTAG = "GeckoEditable";
// Filters to implement Editable's filtering functionality
private InputFilter[] mFilters;
private final AsyncText mText;
@@ -63,17 +66,16 @@ final class GeckoEditable extends IGecko
private Handler mIcPostHandler;
// Parent process child used as a default for key events.
/* package */ IGeckoEditableChild mDefaultChild; // Used by IC thread.
// Parent or content process child that has the focus.
/* package */ IGeckoEditableChild mFocusedChild; // Used by IC thread.
/* package */ IBinder mFocusedToken; // Used by Gecko/binder thread.
/* package */ GeckoEditableListener mListener;
- /* package */ GeckoView mView;
/* package */ boolean mInBatchMode; // Used by IC thread
/* package */ boolean mNeedSync; // Used by IC thread
// Gecko side needs an updated composition from Java;
private boolean mNeedUpdateComposition; // Used by IC thread
private boolean mSuppressKeyUp; // Used by IC thread
private boolean mIgnoreSelectionChange; // Used by Gecko thread
@@ -592,84 +594,57 @@ final class GeckoEditable extends IGecko
if (DEBUG) {
Log.d(LOGTAG, "sending: " + event);
}
onKeyEvent(mFocusedChild, event, event.getAction(),
/* metaState */ 0, /* isSynthesizedImeKey */ true);
}
}
- @WrapForJNI(calledFrom = "gecko")
- private GeckoEditable() {
+ /* package */ GeckoEditable() {
if (DEBUG) {
- // Called by nsWindow.
- ThreadUtils.assertOnGeckoThread();
+ // Called by TextInputController.
+ ThreadUtils.assertOnUiThread();
}
mText = new AsyncText();
mActions = new ConcurrentLinkedQueue<Action>();
final Class<?>[] PROXY_INTERFACES = { Editable.class };
- mProxy = (Editable)Proxy.newProxyInstance(
- Editable.class.getClassLoader(),
- PROXY_INTERFACES, this);
+ mProxy = (Editable) Proxy.newProxyInstance(Editable.class.getClassLoader(),
+ PROXY_INTERFACES, this);
mIcRunHandler = mIcPostHandler = ThreadUtils.getUiHandler();
}
- @WrapForJNI(calledFrom = "gecko")
- private void setDefaultEditableChild(final IGeckoEditableChild child) {
+ /* package */ void setDefaultEditableChild(final IGeckoEditableChild child) {
+ if (DEBUG) {
+ // Called by TextInputController.
+ ThreadUtils.assertOnUiThread();
+ Log.d(LOGTAG, "setDefaultEditableChild " + child);
+ }
mDefaultChild = child;
}
- @WrapForJNI(calledFrom = "gecko")
- private void onViewChange(final GeckoView v) {
+ /* package */ void setListener(final GeckoEditableListener newListener) {
if (DEBUG) {
- // Called by nsWindow.
- ThreadUtils.assertOnGeckoThread();
- Log.d(LOGTAG, "onViewChange(" + v + ")");
+ // Called by TextInputController.
+ ThreadUtils.assertOnUiThread();
+ Log.d(LOGTAG, "setListener " + newListener);
}
- final GeckoEditableListener newListener =
- v != null ? GeckoInputConnection.create(v, this) : null;
-
- final Runnable setListenerRunnable = new Runnable() {
+ mIcPostHandler.post(new Runnable() {
@Override
public void run() {
if (DEBUG) {
Log.d(LOGTAG, "onViewChange (set listener)");
}
mListener = newListener;
}
- };
-
- // Post to UI thread first to make sure any code that is using the old input
- // connection has finished running, before we switch to a new input connection or
- // before we clear the input connection on destruction.
- final Handler icHandler = mIcPostHandler;
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- if (DEBUG) {
- Log.d(LOGTAG, "onViewChange (set IC)");
- }
-
- if (mView != null) {
- // Detach the previous view.
- mView.setInputConnectionListener(null);
- }
- if (v != null) {
- // And attach the new view.
- v.setInputConnectionListener((InputConnectionListener) newListener);
- }
-
- mView = v;
- icHandler.post(setListenerRunnable);
- }
});
}
private boolean onIcThread() {
return mIcRunHandler.getLooper() == Looper.myLooper();
}
private void assertOnIcThread() {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoInputConnection.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoInputConnection.java
@@ -39,19 +39,20 @@ import android.view.View;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
-class GeckoInputConnection
+/* package */ class GeckoInputConnection
extends BaseInputConnection
- implements InputConnectionListener, GeckoEditableListener {
+ implements TextInputController.Delegate,
+ GeckoEditableListener {
private static final boolean DEBUG = false;
protected static final String LOGTAG = "GeckoInputConnection";
private static final String CUSTOM_HANDLER_TEST_METHOD = "testInputConnection";
private static final String CUSTOM_HANDLER_TEST_CLASS =
"org.mozilla.gecko.tests.components.GeckoViewComponent$TextInput";
@@ -65,38 +66,43 @@ class GeckoInputConnection
private String mIMEModeHint = "";
private String mIMEActionHint = "";
private boolean mInPrivateBrowsing;
private boolean mIsUserAction;
private boolean mFocused;
private String mCurrentInputMethod = "";
- private final GeckoView mView;
+ private final GeckoSession mSession;
+ private final View mView;
private final GeckoEditableClient mEditableClient;
protected int mBatchEditCount;
private ExtractedTextRequest mUpdateRequest;
private final ExtractedText mUpdateExtract = new ExtractedText();
private final InputConnection mKeyInputConnection;
private CursorAnchorInfo.Builder mCursorAnchorInfoBuilder;
// Prevent showSoftInput and hideSoftInput from causing reentrant calls on some devices.
private volatile boolean mSoftInputReentrancyGuard;
- public static GeckoEditableListener create(GeckoView targetView,
- GeckoEditableClient editable) {
- if (DEBUG)
- return DebugGeckoInputConnection.create(targetView, editable);
- else
- return new GeckoInputConnection(targetView, editable);
+ public static TextInputController.Delegate create(GeckoSession session,
+ View targetView,
+ GeckoEditableClient editable) {
+ if (DEBUG) {
+ return DebugGeckoInputConnection.create(session, targetView, editable);
+ } else {
+ return new GeckoInputConnection(session, targetView, editable);
+ }
}
- protected GeckoInputConnection(GeckoView targetView,
+ protected GeckoInputConnection(GeckoSession session,
+ View targetView,
GeckoEditableClient editable) {
super(targetView, true);
+ mSession = session;
mView = targetView;
mEditableClient = editable;
mIMEState = IME_STATE_DISABLED;
// InputConnection that sends keys for plugins, which don't have full editors
mKeyInputConnection = new BaseInputConnection(targetView, false);
}
@Override
@@ -197,17 +203,18 @@ class GeckoInputConnection
if ((req.flags & GET_TEXT_WITH_STYLES) != 0) {
extract.text = new SpannableString(editable);
} else {
extract.text = editable.toString();
}
return extract;
}
- private GeckoView getView() {
+ @Override // TextInputController.Delegate
+ public View getView() {
return mView;
}
private InputMethodManager getInputMethodManager() {
View view = getView();
if (view == null) {
return null;
}
@@ -229,23 +236,22 @@ class GeckoInputConnection
@Override
public void run() {
if (v.hasFocus() && !imm.isActive(v)) {
// Marshmallow workaround: The view has focus but it is not the active
// view for the input method. (Bug 1211848)
v.clearFocus();
v.requestFocus();
}
- final GeckoView view = getView();
- if (view != null && view.getSession() != null) {
- if (showToolbar) {
- view.getDynamicToolbarAnimator().showToolbar(/*immediately*/ true);
- }
- view.getEventDispatcher().dispatch("GeckoView:ZoomToInput", null);
+
+ if (showToolbar) {
+ mSession.getDynamicToolbarAnimator().showToolbar(/* immediately */ true);
}
+ mSession.getEventDispatcher().dispatch("GeckoView:ZoomToInput", null);
+
mSoftInputReentrancyGuard = true;
imm.showSoftInput(v, 0);
mSoftInputReentrancyGuard = false;
}
});
}
private void hideSoftInput() {
@@ -352,17 +358,17 @@ class GeckoInputConnection
@TargetApi(21)
@Override
public void updateCompositionRects(final RectF[] rects) {
if (!(Build.VERSION.SDK_INT >= 21)) {
return;
}
- final GeckoView view = getView();
+ final View view = getView();
if (view == null) {
return;
}
final Editable content = getEditable();
if (content == null) {
return;
}
@@ -382,30 +388,26 @@ class GeckoInputConnection
@Override
public void run() {
updateCompositionRectsOnUi(view, rects, composition);
}
});
}
@TargetApi(21)
- /* package */ void updateCompositionRectsOnUi(final GeckoView view,
+ /* package */ void updateCompositionRectsOnUi(final View view,
final RectF[] rects,
final CharSequence composition) {
- if (view.getSession() == null) {
- return;
- }
-
if (mCursorAnchorInfoBuilder == null) {
mCursorAnchorInfoBuilder = new CursorAnchorInfo.Builder();
}
mCursorAnchorInfoBuilder.reset();
final Matrix matrix = new Matrix();
- view.getSession().getClientToScreenMatrix(matrix);
+ mSession.getClientToScreenMatrix(matrix);
mCursorAnchorInfoBuilder.setMatrix(matrix);
for (int i = 0; i < rects.length; i++) {
mCursorAnchorInfoBuilder.addCharacterBounds(
i, rects[i].left, rects[i].top, rects[i].right, rects[i].bottom,
CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION);
}
@@ -472,31 +474,31 @@ class GeckoInputConnection
// wait for new thread to set sBackgroundHandler
GeckoInputConnection.class.wait();
} catch (InterruptedException e) {
}
}
return sBackgroundHandler;
}
- private boolean canReturnCustomHandler() {
+ private synchronized boolean canReturnCustomHandler() {
if (mIMEState == IME_STATE_DISABLED) {
return false;
}
for (StackTraceElement frame : Thread.currentThread().getStackTrace()) {
// We only return our custom Handler to InputMethodManager's InputConnection
// proxy. For all other purposes, we return the regular Handler.
// InputMethodManager retrieves the Handler for its InputConnection proxy
// inside its method startInputInner(), so we check for that here. This is
// valid from Android 2.2 to at least Android 4.2. If this situation ever
// changes, we gracefully fall back to using the regular Handler.
if ("startInputInner".equals(frame.getMethodName()) &&
"android.view.inputmethod.InputMethodManager".equals(frame.getClassName())) {
- // only return our own Handler to InputMethodManager
- return true;
+ // Only return our own Handler to InputMethodManager and only prior to 24.
+ return Build.VERSION.SDK_INT < 24;
}
if (CUSTOM_HANDLER_TEST_METHOD.equals(frame.getMethodName()) &&
CUSTOM_HANDLER_TEST_CLASS.equals(frame.getClassName())) {
// InputConnection tests should also run on the custom handler
return true;
}
}
return false;
@@ -512,34 +514,36 @@ class GeckoInputConnection
}
// Android N: @Override // InputConnection
// We need to suppress lint complaining about the lack override here in the meantime: it wants us to build
// against sdk 24, even though we're using 23, and therefore complains about the lack of override.
// Once we update to 24, we can use the actual override annotation and remove the lint suppression.
@SuppressLint("Override")
public Handler getHandler() {
+ final Handler handler;
if (isPhysicalKeyboardPresent()) {
- return ThreadUtils.getUiHandler();
+ handler = ThreadUtils.getUiHandler();
+ } else {
+ handler = getBackgroundHandler();
}
-
- return getBackgroundHandler();
+ return mEditableClient.setInputConnectionHandler(handler);
}
- @Override // InputConnectionListener
+ @Override // TextInputController.Delegate
public Handler getHandler(Handler defHandler) {
if (!canReturnCustomHandler()) {
return defHandler;
}
- return mEditableClient.setInputConnectionHandler(getHandler());
+ return getHandler();
}
- @Override
- public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ @Override // TextInputController.Delegate
+ public synchronized InputConnection onCreateInputConnection(EditorInfo outAttrs) {
// Some keyboards require us to fill out outAttrs even if we return null.
outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
outAttrs.imeOptions = EditorInfo.IME_ACTION_NONE;
outAttrs.actionLabel = null;
if (mIMEState == IME_STATE_DISABLED) {
hideSoftInput();
return null;
@@ -741,17 +745,17 @@ class GeckoInputConnection
}
@Override
public boolean sendKeyEvent(KeyEvent event) {
sendKeyEvent(event.getAction(), event);
return false; // seems to always return false
}
- @Override
+ @Override // TextInputController.Delegate
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
return false;
}
private boolean shouldProcessKey(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_MENU:
case KeyEvent.KEYCODE_BACK:
@@ -842,22 +846,22 @@ class GeckoInputConnection
@Override
public void run() {
sendKeyEvent(action, event);
}
});
return true;
}
- @Override
+ @Override // TextInputController.Delegate
public boolean onKeyDown(int keyCode, KeyEvent event) {
return processKey(KeyEvent.ACTION_DOWN, keyCode, event);
}
- @Override
+ @Override // TextInputController.Delegate
public boolean onKeyUp(int keyCode, KeyEvent event) {
return processKey(KeyEvent.ACTION_UP, keyCode, event);
}
/**
* Get a key that represents a given character.
*/
private KeyEvent getCharKeyEvent(final char c) {
@@ -871,17 +875,17 @@ class GeckoInputConnection
@Override
public int getUnicodeChar(int metaState) {
return c;
}
};
}
- @Override
+ @Override // TextInputController.Delegate
public boolean onKeyMultiple(int keyCode, int repeatCount, final KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
// KEYCODE_UNKNOWN means the characters are in KeyEvent.getCharacters()
final String str = event.getCharacters();
for (int i = 0; i < str.length(); i++) {
final KeyEvent charEvent = getCharKeyEvent(str.charAt(i));
if (!processKey(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_UNKNOWN, charEvent) ||
!processKey(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_UNKNOWN, charEvent)) {
@@ -895,33 +899,33 @@ class GeckoInputConnection
if (!processKey(KeyEvent.ACTION_DOWN, keyCode, event) ||
!processKey(KeyEvent.ACTION_UP, keyCode, event)) {
return false;
}
}
return true;
}
- @Override
+ @Override // TextInputController.Delegate
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
View v = getView();
switch (keyCode) {
case KeyEvent.KEYCODE_MENU:
InputMethodManager imm = getInputMethodManager();
imm.toggleSoftInputFromWindow(v.getWindowToken(),
InputMethodManager.SHOW_FORCED, 0);
return true;
default:
break;
}
return false;
}
- @Override
- public boolean isIMEEnabled() {
+ @Override // TextInputController.Delegate
+ public synchronized boolean isInputActive() {
// make sure this picks up PASSWORD and PLUGIN states as well
return mIMEState != IME_STATE_DISABLED;
}
@Override
public void notifyIME(int type) {
switch (type) {
@@ -968,18 +972,19 @@ class GeckoInputConnection
if (DEBUG) {
throw new IllegalArgumentException("Unexpected NOTIFY_IME=" + type);
}
break;
}
}
@Override
- public void notifyIMEContext(int state, String typeHint, String modeHint, String actionHint,
- boolean inPrivateBrowsing, boolean isUserAction) {
+ public synchronized void notifyIMEContext(int state, String typeHint, String modeHint,
+ String actionHint, boolean inPrivateBrowsing,
+ boolean isUserAction) {
// For some input type we will use a widget to display the ui, for those we must not
// display the ime. We can display a widget for date and time types and, if the sdk version
// is 11 or greater, for datetime/month/week as well.
if (typeHint != null &&
(typeHint.equalsIgnoreCase("date") ||
typeHint.equalsIgnoreCase("time") ||
typeHint.equalsIgnoreCase("datetime") ||
typeHint.equalsIgnoreCase("month") ||
@@ -1030,33 +1035,35 @@ class GeckoInputConnection
final class DebugGeckoInputConnection
extends GeckoInputConnection
implements InvocationHandler {
private InputConnection mProxy;
private final StringBuilder mCallLevel;
- private DebugGeckoInputConnection(GeckoView targetView,
+ private DebugGeckoInputConnection(GeckoSession session,
+ View targetView,
GeckoEditableClient editable) {
- super(targetView, editable);
+ super(session, targetView, editable);
mCallLevel = new StringBuilder();
}
- public static GeckoEditableListener create(GeckoView targetView,
- GeckoEditableClient editable) {
+ public static TextInputController.Delegate create(GeckoSession session,
+ View targetView,
+ GeckoEditableClient editable) {
final Class<?>[] PROXY_INTERFACES = { InputConnection.class,
- InputConnectionListener.class,
+ TextInputController.Delegate.class,
GeckoEditableListener.class };
DebugGeckoInputConnection dgic =
- new DebugGeckoInputConnection(targetView, editable);
- dgic.mProxy = (InputConnection)Proxy.newProxyInstance(
+ new DebugGeckoInputConnection(session, targetView, editable);
+ dgic.mProxy = (InputConnection) Proxy.newProxyInstance(
GeckoInputConnection.class.getClassLoader(),
PROXY_INTERFACES, dgic);
- return (GeckoEditableListener)dgic.mProxy;
+ return (TextInputController.Delegate) dgic.mProxy;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
StringBuilder log = new StringBuilder(mCallLevel);
log.append("> ").append(method.getName()).append("(");