Bug 1416918 - 1. Add TextInputController; r=esawin

Add TextInputController and add a getter for it in GeckoSession.
TextInputController is used to process key events and to interact with
the input method manager.

MozReview-Commit-ID: 1j2Moqukf8U

--HG--
extra : rebase_source : 03fee51ba3e2034b8689c4fa6fe58f7432dc1ad4
This commit is contained in:
Jim Chen 2017-12-13 22:57:21 -05:00
Родитель 5fedbb92b8
Коммит 6075204d7c
5 изменённых файлов: 261 добавлений и 16 удалений

Просмотреть файл

@ -451,6 +451,7 @@ gvjar.sources += [geckoview_source_dir + 'java/org/mozilla/gecko/' + x
'sqlite/MatrixBlobCursor.java',
'sqlite/SQLiteBridge.java',
'sqlite/SQLiteBridgeException.java',
'TextInputController.java',
'TouchEventInterceptor.java',
'WakeLockDelegate.java',
]]

Просмотреть файл

@ -16,6 +16,7 @@ import org.mozilla.gecko.mozglue.JNIObject;
import org.mozilla.gecko.util.BundleEventListener;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoBundle;
import org.mozilla.gecko.util.ThreadUtils;
import android.content.ContentResolver;
import android.content.Context;
@ -28,6 +29,7 @@ import android.os.IInterface;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
@ -65,6 +67,8 @@ public class GeckoSession extends LayerSession
private final EventDispatcher mEventDispatcher =
new EventDispatcher(mNativeQueue);
private final TextInputController mTextInput = new TextInputController(this);
private final GeckoSessionHandler<ContentListener> mContentHandler =
new GeckoSessionHandler<ContentListener>(
"GeckoViewContent", this,
@ -328,6 +332,10 @@ public class GeckoSession extends LayerSession
@WrapForJNI(dispatchTo = "proxy")
public native void attach(GeckoView view);
@WrapForJNI(dispatchTo = "proxy")
public native void attachEditable(IGeckoEditableParent parent,
GeckoEditableChild child);
@WrapForJNI(calledFrom = "gecko")
private synchronized void onReady() {
if (mNativeQueue.checkAndSetState(State.INITIAL, State.READY)) {
@ -471,6 +479,8 @@ public class GeckoSession extends LayerSession
}
public void openWindow(final Context appContext) {
ThreadUtils.assertOnUiThread();
if (isOpen()) {
throw new IllegalStateException("Session is open");
}
@ -501,6 +511,10 @@ public class GeckoSession extends LayerSession
String.class, chromeUri,
screenId, isPrivate);
}
if (mTextInput != null) {
mTextInput.onWindowReady(mNativeQueue, mWindow);
}
}
public void attachView(final GeckoView view) {
@ -531,6 +545,16 @@ public class GeckoSession extends LayerSession
mWindow = null;
}
/**
* Get the TextInputController instance for this session.
*
* @return TextInputController instance.
*/
public @NonNull TextInputController getTextInputController() {
// May be called on any thread.
return mTextInput;
}
/**
* Load the given URI.
* @param uri The URI of the resource to load.

Просмотреть файл

@ -0,0 +1,213 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko;
import org.mozilla.gecko.util.ThreadUtils;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
/**
* TextInputController handles text input for GeckoSession through key events or input
* methods. It is typically used to implement certain methods in View such as {@code
* onCreateInputConnection()}, by forwarding such calls to corresponding methods in
* TextInputController.
*/
public final class TextInputController {
/* package */ interface Delegate {
View getView();
Handler getHandler(Handler defHandler);
InputConnection onCreateInputConnection(EditorInfo attrs);
boolean onKeyPreIme(int keyCode, KeyEvent event);
boolean onKeyDown(int keyCode, KeyEvent event);
boolean onKeyUp(int keyCode, KeyEvent event);
boolean onKeyLongPress(int keyCode, KeyEvent event);
boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event);
boolean isInputActive();
}
private final GeckoSession mSession;
private final GeckoEditable mEditable = new GeckoEditable();
private final GeckoEditableChild mEditableChild = new GeckoEditableChild(mEditable);
private Delegate mInputConnection;
/* package */ TextInputController(final @NonNull GeckoSession session) {
mSession = session;
mEditable.setDefaultEditableChild(mEditableChild);
}
/* package */ void onWindowReady(final NativeQueue queue,
final GeckoSession.Window window) {
if (queue.isReady()) {
window.attachEditable(mEditable, mEditableChild);
} else {
queue.queueUntilReady(window, "attachEditable",
IGeckoEditableParent.class, mEditable,
GeckoEditableChild.class, mEditableChild);
}
}
/**
* Get a Handler for the background input method thread. In order to use a background
* thread for input method operations on systems prior to Nougat, first override
* {@code View.getHandler()} for the View returning the InputConnection instance, and
* then call this method from the overridden method.
*
* For example: <pre>{@code
* @Override
* public Handler getHandler() {
* if (Build.VERSION.SDK_INT >= 24) {
* return super.getHandler();
* }
* return getSession().getTextInputController().getHandler(super.getHandler());
* }
* }</pre>
*
* @param defHandler Handler returned by the system {@code getHandler} implementation.
* @return Handler to return to the system through {@code getHandler}.
*/
public @NonNull Handler getHandler(final @NonNull Handler defHandler) {
// May be called on any thread.
if (mInputConnection != null) {
return mInputConnection.getHandler(defHandler);
}
return defHandler;
}
private synchronized void ensureInputConnection() {
if (mInputConnection == null) {
mInputConnection = GeckoInputConnection.create(mSession,
/* view */ null,
mEditable);
mEditable.setListener((GeckoEditableListener) mInputConnection);
}
}
/**
* Get the current View for text input.
*
* @return Current text input View or null if not set.
* @see #setView(View)
*/
public @Nullable View getView() {
ThreadUtils.assertOnUiThread();
return mInputConnection != null ? mInputConnection.getView() : null;
}
/**
* Set the View for text input. The current View is used to interact with the system
* input method manager and to display certain text input UI elements.
*
* @param view Text input View or null to clear current View.
*/
public synchronized void setView(final @Nullable View view) {
ThreadUtils.assertOnUiThread();
if (view == null) {
mInputConnection = null;
} else if (mInputConnection == null || mInputConnection.getView() != view) {
mInputConnection = GeckoInputConnection.create(mSession, view, mEditable);
}
mEditable.setListener((GeckoEditableListener) mInputConnection);
}
/**
* Get an InputConnection instance. For full functionality, call {@link
* #setView(View)} first before calling this method.
*
* @param outAttrs EditorInfo instance to be filled on return.
* @return InputConnection instance or null if input method is not active.
*/
public synchronized @Nullable InputConnection onCreateInputConnection(
final @NonNull EditorInfo attrs) {
// May be called on any thread.
ensureInputConnection();
return mInputConnection.onCreateInputConnection(attrs);
}
/**
* Process a KeyEvent as a pre-IME event.
*
* @param keyCode Key code.
* @param event KeyEvent instance.
* @return True if the event was handled.
*/
public boolean onKeyPreIme(final int keyCode, final @NonNull KeyEvent event) {
ThreadUtils.assertOnUiThread();
ensureInputConnection();
return mInputConnection.onKeyPreIme(keyCode, event);
}
/**
* Process a KeyEvent as a key-down event.
*
* @param keyCode Key code.
* @param event KeyEvent instance.
* @return True if the event was handled.
*/
public boolean onKeyDown(final int keyCode, final @NonNull KeyEvent event) {
ThreadUtils.assertOnUiThread();
ensureInputConnection();
return mInputConnection.onKeyDown(keyCode, event);
}
/**
* Process a KeyEvent as a key-up event.
*
* @param keyCode Key code.
* @param event KeyEvent instance.
* @return True if the event was handled.
*/
public boolean onKeyUp(final int keyCode, final @NonNull KeyEvent event) {
ThreadUtils.assertOnUiThread();
ensureInputConnection();
return mInputConnection.onKeyUp(keyCode, event);
}
/**
* Process a KeyEvent as a long-press event.
*
* @param keyCode Key code.
* @param event KeyEvent instance.
* @return True if the event was handled.
*/
public boolean onKeyLongPress(final int keyCode, final @NonNull KeyEvent event) {
ThreadUtils.assertOnUiThread();
ensureInputConnection();
return mInputConnection.onKeyLongPress(keyCode, event);
}
/**
* Process a KeyEvent as a multiple-press event.
*
* @param keyCode Key code.
* @param event KeyEvent instance.
* @return True if the event was handled.
*/
public boolean onKeyMultiple(final int keyCode, final int repeatCount,
final @NonNull KeyEvent event) {
ThreadUtils.assertOnUiThread();
ensureInputConnection();
return mInputConnection.onKeyMultiple(keyCode, repeatCount, event);
}
/**
* Return whether there is an active input connection, usually as a result of a
* focused input field.
*
* @return True if input is active.
*/
public boolean isInputActive() {
ThreadUtils.assertOnUiThread();
return mInputConnection != null && mInputConnection.isInputActive();
}
}

Просмотреть файл

@ -301,6 +301,10 @@ public:
void Attach(const GeckoSession::Window::LocalRef& inst,
jni::Object::Param aView);
void AttachEditable(const GeckoSession::Window::LocalRef& inst,
jni::Object::Param aEditableParent,
jni::Object::Param aEditableChild);
void EnableEventDispatcher();
};
@ -1227,10 +1231,10 @@ EGLSurface nsWindow::PMPMSupport::sSurface;
nsWindow::GeckoViewSupport::~GeckoViewSupport()
{
// Disassociate our GeckoEditable instance with our native object.
MOZ_ASSERT(window.mEditableSupport && window.mEditable);
if (window.mEditableSupport) {
window.mEditableSupport.Detach();
window.mEditable->OnViewChange(nullptr);
window.mEditable = nullptr;
window.mEditableParent = nullptr;
}
if (window.mNPZCSupport) {
window.mNPZCSupport.Detach();
@ -1302,13 +1306,6 @@ nsWindow::GeckoViewSupport::Open(const jni::Class::LocalRef& aCls,
window->mGeckoViewSupport->Transfer(
sessionWindow, aCompositor, aDispatcher, aSettings);
// Attach a new GeckoEditable support object to the new window.
auto editable = GeckoEditable::New();
auto editableChild = GeckoEditableChild::New(editable);
editable->SetDefaultEditableChild(editableChild);
window->mEditable = editable;
window->mEditableSupport.Attach(editableChild, window, editableChild);
if (window->mWidgetListener) {
nsCOMPtr<nsIXULWindow> xulWindow(
window->mWidgetListener->GetXULWindow());
@ -1378,9 +1375,19 @@ void
nsWindow::GeckoViewSupport::Attach(const GeckoSession::Window::LocalRef& inst,
jni::Object::Param aView)
{
// Associate our previous GeckoEditable with the new GeckoView.
MOZ_ASSERT(window.mEditable);
window.mEditable->OnViewChange(aView);
}
void
nsWindow::GeckoViewSupport::AttachEditable(const GeckoSession::Window::LocalRef& inst,
jni::Object::Param aEditableParent,
jni::Object::Param aEditableChild)
{
java::GeckoEditableChild::LocalRef editableChild(inst.Env());
editableChild = java::GeckoEditableChild::Ref::From(aEditableChild);
MOZ_ASSERT(!window.mEditableSupport);
window.mEditableSupport.Attach(editableChild, &window, editableChild);
window.mEditableParent = aEditableParent;
}
void

Просмотреть файл

@ -184,7 +184,7 @@ private:
// Object that implements native GeckoEditable calls.
// Strong referenced by the Java instance.
NativePtr<mozilla::widget::GeckoEditableSupport> mEditableSupport;
mozilla::java::GeckoEditable::GlobalRef mEditable;
mozilla::jni::Object::GlobalRef mEditableParent;
class GeckoViewSupport;
// Object that implements native GeckoView calls and associated states.
@ -309,7 +309,7 @@ public:
// event (like a keypress or mouse click).
void UserActivity();
mozilla::java::GeckoEditable::Ref& GetEditableParent() { return mEditable; }
mozilla::jni::Object::Ref& GetEditableParent() { return mEditableParent; }
void RecvToolbarAnimatorMessageFromCompositor(int32_t aMessage) override;
void UpdateRootFrameMetrics(const ScreenPoint& aScrollOffset, const CSSToScreenScale& aZoom) override;