| 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..cb29b82fbb6650d640b5711fd5ef7220bf825050
|
| --- /dev/null
|
| +++ b/content/public/android/java/src/org/chromium/content/browser/input/ChromiumInputConnectionFactory.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 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 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 ChromiumInputConnection(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)
|
| + 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.");
|
| + }
|
| + });
|
| + }
|
| +}
|
|
|