Index: content/public/android/java/src/org/chromium/content/browser/input/ChromiumBaseInputConnectionFactory.java |
diff --git a/content/public/android/java/src/org/chromium/content/browser/input/ChromiumBaseInputConnectionFactory.java b/content/public/android/java/src/org/chromium/content/browser/input/ChromiumBaseInputConnectionFactory.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..04734f35a6ff9b45a21a083f63c317ed99bffaf1 |
--- /dev/null |
+++ b/content/public/android/java/src/org/chromium/content/browser/input/ChromiumBaseInputConnectionFactory.java |
@@ -0,0 +1,153 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.content.browser.input; |
+ |
+import android.content.Context; |
+import android.os.Handler; |
+import android.os.Looper; |
+import android.os.Message; |
+import android.os.MessageQueue; |
+import android.text.Editable; |
+import android.view.View; |
+import android.view.inputmethod.EditorInfo; |
+import android.view.inputmethod.InputMethodManager; |
+ |
+import org.chromium.base.CommandLine; |
+import org.chromium.base.Log; |
+import org.chromium.content.common.ContentSwitches; |
+ |
+import java.lang.reflect.Constructor; |
+import java.lang.reflect.Field; |
+import java.lang.reflect.InvocationTargetException; |
+ |
+/** |
+ * Default factory for ChromiumBaseInputConnection classes. |
+ */ |
+public class ChromiumBaseInputConnectionFactory { |
+ private static final String TAG = "cr.Ime"; |
+ |
+ private ChromiumInputConnection mChromiumInputConnection; |
+ private View mView; |
aelias_OOO_until_Jul13
2015/09/30 00:10:03
This member variable is only used to recreate if t
Changwan Ryu
2016/01/19 07:31:53
Removed and moved the logic up to ImeAdapter.
|
+ |
+ public ChromiumBaseInputConnection get( |
+ View view, ImeAdapter imeAdapter, Editable editable, EditorInfo outAttrs) { |
+ if (CommandLine.getInstance().hasSwitch(ContentSwitches.USE_IME_THREAD)) { |
+ ImeUtils.assertOnUiThread(); |
+ if (mChromiumInputConnection == null || view != mView) { |
+ Log.w(TAG, "Creating ChromiumInputConnection..."); |
+ mChromiumInputConnection = new ChromiumInputConnection(view, imeAdapter); |
+ ImeUtils.assertReally("View: " + view, mView == null || view == mView); |
+ mView = view; |
+ } |
+ mChromiumInputConnection.reset(); |
+ mChromiumInputConnection.updateEditorInfo( |
+ outAttrs, imeAdapter.getTextInputType(), imeAdapter.getTextInputFlags()); |
+ switchInputConnectionLooper(view.getContext(), view.getHandler()); |
+ return mChromiumInputConnection; |
+ } else { |
+ return new AdapterInputConnection(view, imeAdapter, editable, outAttrs); |
+ } |
+ } |
+ |
+ /** |
+ * Pump messages from a handler and add them to another handler. |
+ * |
+ * @param sourceHandler The source handler. |
+ * @param targetHandler The target handler. |
+ * @throws NoSuchFieldException |
+ * @throws IllegalAccessException |
+ * @throws IllegalArgumentException |
+ */ |
+ private void pumpHandlerMessages(Handler sourceHandler, Handler targetHandler) |
+ throws NoSuchFieldException, IllegalAccessException, IllegalArgumentException { |
+ MessageQueue queue = sourceHandler.getLooper().getQueue(); |
+ Field messagesField = MessageQueue.class.getDeclaredField("mMessages"); |
+ Field nextField = Message.class.getDeclaredField("next"); |
+ |
+ synchronized (queue) { |
+ // Copy messages first. |
+ messagesField.setAccessible(true); |
+ nextField.setAccessible(true); |
+ Message msg = (Message) messagesField.get(queue); |
+ while (msg != null) { |
+ if (msg.getTarget() == sourceHandler) { |
+ Log.d(TAG, "Copying a message to the new handler..."); |
+ Message newMsg = targetHandler.obtainMessage(); |
+ newMsg.copyFrom(msg); |
+ targetHandler.sendMessage(newMsg); |
+ } |
+ msg = (Message) nextField.get(msg); |
+ } |
+ messagesField.setAccessible(false); |
+ nextField.setAccessible(false); |
+ |
+ // Now remove messages from the first handler. |
+ sourceHandler.removeCallbacksAndMessages(null); |
aelias_OOO_until_Jul13
2015/09/18 04:59:30
Is there any signal we can use to filter for IME-r
Changwan Ryu
2015/09/18 07:34:59
This removes callbacks and messages that belong to
|
+ } |
+ } |
+ |
+ /** |
+ * Switch the looper in InputMethodManager so that future calls to InputConnection |
+ * can run in IME thread. |
+ * TODO(changwan): add more description |
+ * |
+ * @param context |
+ * @param viewHandler |
+ */ |
+ private void switchInputConnectionLooper(Context context, Handler viewHandler) { |
Changwan Ryu
2015/09/17 06:58:01
Alex, this is the logic that implements the idea w
|
+ Log.d(TAG, "switchInputConnectionLooper"); |
+ // Retrieve the same singleton instance of InputMethodManager that called |
+ // onCreateInputConnection(). |
+ final InputMethodManager inputMethodManager = |
+ (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); |
+ final Handler icHandler = InputConnectionHandlerFactory.getInputConnectionHandler(); |
+ viewHandler.post(new Runnable() { |
+ @Override |
+ public void run() { |
+ Log.d(TAG, "switchInputConnectionLooper: replacing looper and handler"); |
+ try { |
+ // inputMethodManager.mServedInputConnectionWrapper.mMainLooper = |
+ // icHandler.getLooper(); |
+ Field inputConnectionWrapperField = InputMethodManager.class.getDeclaredField( |
+ "mServedInputConnectionWrapper"); |
+ inputConnectionWrapperField.setAccessible(true); |
+ Object inputConnectionWrapper = |
+ inputConnectionWrapperField.get(inputMethodManager); |
+ Class<?> iinputConnectionWrapperClass = |
+ Class.forName("com.android.internal.view.IInputConnectionWrapper"); |
+ Field looperField = |
+ iinputConnectionWrapperClass.getDeclaredField("mMainLooper"); |
+ looperField.setAccessible(true); |
+ looperField.set(inputConnectionWrapper, icHandler.getLooper()); |
+ looperField.setAccessible(false); |
+ |
+ // inputMethodManager.mServedInputConnectionWrapper.mH = |
+ // new IInputConnectionWrapper.MyHandler(icHandler.getLooper()); |
+ Class<?> myHandlerClass = Class.forName( |
+ "com.android.internal.view.IInputConnectionWrapper$MyHandler"); |
+ Constructor<?> ctor = myHandlerClass.getDeclaredConstructor( |
+ iinputConnectionWrapperClass, Looper.class); |
+ ctor.setAccessible(true); |
+ Handler myHandler = (Handler) ctor.newInstance( |
+ inputConnectionWrapper, icHandler.getLooper()); |
+ Field handlerField = iinputConnectionWrapperClass.getDeclaredField("mH"); |
+ ctor.setAccessible(false); |
+ handlerField.setAccessible(true); |
+ Handler oldHandler = (Handler) handlerField.get(inputConnectionWrapper); |
+ handlerField.set(inputConnectionWrapper, myHandler); |
+ handlerField.setAccessible(false); |
+ |
+ pumpHandlerMessages(oldHandler, myHandler); |
+ } catch (IllegalAccessException | IllegalArgumentException | NoSuchFieldException |
+ | ClassNotFoundException | NoSuchMethodException | InstantiationException |
+ | InvocationTargetException | NullPointerException e) { |
+ e.printStackTrace(); |
+ return; |
+ } |
+ Log.d(TAG, "switchInputConnectionLooper: replacing done."); |
+ } |
+ }); |
+ } |
+} |