Chromium Code Reviews| Index: content/public/android/java/src/org/chromium/content/browser/input/ChromiumInputConnectionFactory.java |
| diff --git a/content/public/android/java/src/org/chromium/content/browser/input/ChromiumInputConnectionFactory.java b/content/public/android/java/src/org/chromium/content/browser/input/ChromiumInputConnectionFactory.java |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..7a99d5aeac69c7524604a707e27182225d351f3c |
| --- /dev/null |
| +++ b/content/public/android/java/src/org/chromium/content/browser/input/ChromiumInputConnectionFactory.java |
| @@ -0,0 +1,162 @@ |
| +// 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 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 ChromiumInputConnectionFactory implements ChromiumBaseInputConnection.Factory { |
| + private static final String TAG = "cr_Ime"; |
| + |
| + private ChromiumInputConnection mChromiumInputConnection; |
| + private final Handler mHandler; |
| + private final ChromiumInputConnection.ThreadManager mThreadManager; |
| + |
| + ChromiumInputConnectionFactory() { |
| + HandlerThread thread = |
| + new HandlerThread("InputConnectionHandlerThread", HandlerThread.NORM_PRIORITY); |
| + thread.start(); |
| + mHandler = new Handler(thread.getLooper()); |
| + mThreadManager = new ChromiumInputConnection.ThreadManager(mHandler); |
| + } |
| + |
| + @Override |
| + public ChromiumBaseInputConnection get(View view, ImeAdapter imeAdapter, int inputType, |
|
aelias_OOO_until_Jul13
2016/01/21 07:43:04
Since this method is destructive, I'd prefer it be
Changwan Ryu
2016/01/22 10:22:16
Changed to initializeAndGet.
|
| + int inputFlags, EditorInfo outAttrs) { |
| + ImeUtils.assertOnUiThread(); |
| + if (mChromiumInputConnection == null) { |
| + Log.w(TAG, "Creating ChromiumInputConnection..."); |
|
aelias_OOO_until_Jul13
2016/01/21 07:43:04
Log.d
Changwan Ryu
2016/01/22 10:22:16
Done.
|
| + mChromiumInputConnection = new ChromiumInputConnection(imeAdapter, mThreadManager); |
| + } |
| + mChromiumInputConnection.initializeOnUiThread(inputType, inputFlags, outAttrs); |
| + switchInputConnectionLooper(view.getContext(), view.getHandler()); |
| + return mChromiumInputConnection; |
| + } |
| + |
| + /** |
| + * 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) |
| + 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); |
| + } |
| + 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(); |
| + return; |
| + } |
| + Log.d(TAG, "switchInputConnectionLooper: replacing done."); |
| + } |
| + }); |
| + } |
| +} |