Index: content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionFactory.java |
diff --git a/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionFactory.java b/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionFactory.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..3ebfa85722be9e0d8afd1bc3b9226b5f791faf69 |
--- /dev/null |
+++ b/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionFactory.java |
@@ -0,0 +1,168 @@ |
+// Copyright 2016 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.HandlerThread; |
+import android.os.Looper; |
+import android.os.Message; |
+import android.os.MessageQueue; |
+import android.view.View; |
+import android.view.inputmethod.EditorInfo; |
+import android.view.inputmethod.InputMethodManager; |
+ |
+import org.chromium.base.Log; |
+import org.chromium.content.browser.input.ChromiumBaseInputConnection.ThreadManager; |
+ |
+import java.lang.reflect.Constructor; |
+import java.lang.reflect.Field; |
+import java.lang.reflect.InvocationTargetException; |
+import java.lang.reflect.Method; |
+ |
+/** |
+ * Default factory for ChromiumBaseInputConnection classes. |
+ */ |
+public class ThreadedInputConnectionFactory implements ChromiumBaseInputConnection.Factory { |
+ private static final String TAG = "cr_Ime"; |
+ |
+ private ThreadedInputConnection mChromiumInputConnection; |
+ private final Handler mHandler; |
+ private final ThreadedInputConnection.ThreadManager mThreadManager; |
+ |
+ ThreadedInputConnectionFactory() { |
+ HandlerThread thread = |
+ new HandlerThread("InputConnectionHandlerThread", HandlerThread.NORM_PRIORITY); |
+ thread.start(); |
+ mHandler = new Handler(thread.getLooper()); |
+ mThreadManager = new ThreadManager(mHandler); |
+ } |
+ |
+ @Override |
+ public ChromiumBaseInputConnection initializeAndGet(View view, ImeAdapter imeAdapter, |
+ int inputType, int inputFlags, EditorInfo outAttrs) { |
+ ImeUtils.assertOnUiThread(); |
+ if (mChromiumInputConnection == null) { |
+ Log.d(TAG, "Creating ChromiumInputConnection..."); |
+ mChromiumInputConnection = new ThreadedInputConnection(imeAdapter, mThreadManager); |
+ } |
+ mChromiumInputConnection.initializeOutAttrsOnUiThread(inputType, inputFlags, outAttrs); |
+ switchInputConnectionLooper(view.getContext(), view.getHandler()); |
+ return mChromiumInputConnection; |
+ } |
+ |
+ @Override |
+ public ThreadManager getThreadManager() { |
+ return mThreadManager; |
+ } |
+ |
+ /** |
+ * 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 |
+ * @throws NoSuchMethodException |
+ * @throws InvocationTargetException |
+ */ |
+ private void pumpHandlerMessages(Handler sourceHandler, Handler targetHandler) |
Ted C
2016/02/02 23:14:43
yikes...this method is...terrifying.
I know your
aelias_OOO_until_Jul13
2016/02/02 23:51:56
I chatted offline with Ted about this. Here's wha
Changwan Ryu
2016/02/03 00:02:47
Thanks for the feedback and new idea. I'll investi
Changwan Ryu
2016/02/11 16:21:08
I've explored many different ideas, and switched t
|
+ throws NoSuchFieldException, IllegalAccessException, IllegalArgumentException, |
+ NoSuchMethodException, InvocationTargetException { |
+ Looper looper = sourceHandler.getLooper(); |
+ Method getQueueMethod = Looper.class.getDeclaredMethod("getQueue"); |
+ getQueueMethod.setAccessible(true); |
+ MessageQueue queue = (MessageQueue) getQueueMethod.invoke(looper); |
+ getQueueMethod.setAccessible(false); |
+ |
+ 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); |
Ted C
2016/02/02 23:14:43
This looks like it would clobber any delayed-ness
Changwan Ryu
2016/02/11 16:21:08
I've switched to another approach. I'll fix this o
|
+ } |
+ msg = (Message) nextField.get(msg); |
+ } |
+ messagesField.setAccessible(false); |
+ nextField.setAccessible(false); |
+ |
+ // Now remove messages from the first handler. |
+ sourceHandler.removeCallbacksAndMessages(null); |
+ } |
+ } |
+ |
+ /** |
+ * Switch the looper in InputMethodManager so that future calls to InputConnection |
+ * can run in IME thread. |
+ * |
+ * @param context The context |
+ * @param viewHandler The current handler for the View that this InputConnection will be |
+ * attached to. |
+ */ |
+ private void switchInputConnectionLooper(Context context, Handler viewHandler) { |
+ 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 = mHandler; |
+ 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(); |
Ted C
2016/02/02 23:14:43
I would use Log.wtf here.
This is unrecoverable r
Changwan Ryu
2016/02/11 16:21:08
I've added verification & crashing logic in Thread
|
+ return; |
+ } |
+ Log.d(TAG, "switchInputConnectionLooper: replacing done."); |
+ } |
+ }); |
+ } |
+} |