Chromium Code Reviews| 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..ffd0ad3f38c08fb1a3774fc0593e188683b428bd |
| --- /dev/null |
| +++ b/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionFactory.java |
| @@ -0,0 +1,167 @@ |
| +// 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.os.Handler; |
| +import android.os.HandlerThread; |
| +import android.view.View; |
| +import android.view.inputmethod.EditorInfo; |
| + |
| +import org.chromium.base.CommandLine; |
| +import org.chromium.base.Log; |
| +import org.chromium.base.ThreadUtils; |
| +import org.chromium.base.VisibleForTesting; |
| +import org.chromium.content.common.ContentSwitches; |
| + |
| +/** |
| + * A factory class for {@link ThreadedInputConnection}. The class also includes triggering |
| + * mechanism (hack) to run our InputConnection on non-UI thread. |
| + */ |
| +// TODO(changwan): add unit tests once Robolectric supports Android API level >= 21. |
| +// See crbug.com/588547 for details. |
| +public class ThreadedInputConnectionFactory implements ChromiumBaseInputConnection.Factory { |
| + private static final String TAG = "cr_Ime"; |
| + private static final boolean DEBUG_LOGS = false; |
| + |
| + private final Handler mHandler; |
| + private final InputMethodManagerWrapper mInputMethodManagerWrapper; |
| + private final ImeAdapter mImeAdapter; |
| + private final InputMethodUma mInputMethodUma; |
| + private ThreadedInputConnectionProxyView mProxyView; |
| + private ThreadedInputConnection mThreadedInputConnection; |
| + |
| + ThreadedInputConnectionFactory( |
| + InputMethodManagerWrapper inputMethodManagerWrapper, ImeAdapter imeAdapter) { |
| + mImeAdapter = imeAdapter; |
| + mInputMethodManagerWrapper = inputMethodManagerWrapper; |
| + mHandler = createHandler(); |
| + mInputMethodUma = createInputMethodUma(); |
| + } |
| + |
| + @VisibleForTesting |
| + protected Handler createHandler() { |
| + HandlerThread thread = |
| + new HandlerThread("InputConnectionHandlerThread", HandlerThread.NORM_PRIORITY); |
| + thread.start(); |
| + return new Handler(thread.getLooper()); |
| + } |
| + |
| + @VisibleForTesting |
| + protected ThreadedInputConnectionProxyView createProxyView( |
| + Handler handler, View containerView) { |
| + return new ThreadedInputConnectionProxyView( |
| + containerView.getContext(), handler, containerView); |
| + } |
| + |
| + @VisibleForTesting |
| + protected InputMethodUma createInputMethodUma() { |
| + return new InputMethodUma(); |
| + } |
| + |
| + private boolean shouldTriggerDelayedOnCreateInputConnection() { |
| + for (StackTraceElement ste : Thread.currentThread().getStackTrace()) { |
| + String className = ste.getClassName(); |
| + if (className != null |
| + && (className.contains(ThreadedInputConnectionProxyView.class.getName()) |
| + || className.contains("TestInputMethodManagerWrapper"))) { |
| + return false; |
| + } |
| + } |
| + return true; |
| + } |
| + |
| + @Override |
| + public ThreadedInputConnection initializeAndGet( |
| + View view, ImeAdapter imeAdapter, int inputType, int inputFlags, int selectionStart, |
| + int selectionEnd, EditorInfo outAttrs) { |
| + ImeUtils.checkOnUiThread(); |
| + if (shouldTriggerDelayedOnCreateInputConnection()) { |
| + triggerDelayedOnCreateInputConnection(view); |
| + return null; |
| + } |
| + if (DEBUG_LOGS) Log.w(TAG, "initializeAndGet: called from proxy view"); |
| + if (mThreadedInputConnection == null) { |
| + if (DEBUG_LOGS) Log.w(TAG, "Creating ThreadedInputConnection..."); |
| + mThreadedInputConnection = new ThreadedInputConnection(imeAdapter, mHandler); |
| + } |
| + mThreadedInputConnection.initializeOutAttrsOnUiThread(inputType, inputFlags, |
| + selectionStart, selectionEnd, outAttrs); |
| + return mThreadedInputConnection; |
| + } |
| + |
| + private void triggerDelayedOnCreateInputConnection(final View view) { |
| + if (DEBUG_LOGS) Log.w(TAG, "triggerDelayedOnCreateInputConnection"); |
| + if (mProxyView == null) { |
| + mProxyView = createProxyView(mHandler, view); |
| + } |
| + mProxyView.requestFocus(); |
| + view.getHandler().post(new Runnable() { |
| + @Override |
| + public void run() { |
| + // This is a hack to make InputMethodManager believe that the proxy view |
| + // now has a focus. As a result, InputMethodManager will think that mProxyView |
| + // is focused, and will call getHandler() of the view when creating input |
| + // connection. |
| + |
| + // Step 1: Set mProxyView as InputMethodManager#mNextServedView. |
| + mProxyView.onWindowFocusChanged(true); |
| + |
| + // Step 2: Have InputMethodManager focus in on mNextServedView. |
| + // As a result, IMM will call onCreateInputConnection() on mProxyView on the same |
| + // thread as mProxyView.getHandler(). It will also call subsequent InputConnection |
| + // methods on this IME thread. |
| + mInputMethodManagerWrapper.isActive(view); |
| + |
| + // Step 3: Check that the above hack worked. |
| + mHandler.post(new Runnable() { |
| + @Override |
| + public void run() { |
| + // Some other view already took focus. Container view should be active |
| + // otherwise regardless of whether proxy view is registered or not. |
| + if (!mInputMethodManagerWrapper.isActive(view)) return; |
| + |
| + // Success. |
| + if (mInputMethodManagerWrapper.isActive(mProxyView)) { |
| + mInputMethodUma.recordProxyViewSuccess(); |
| + return; |
| + } |
| + |
| + if (mThreadedInputConnection == null) { |
| + // First time and failed. It is highly likely that this does not work |
| + // systematically. |
| + Log.w(TAG, "Failed to register proxy view, falling back to " |
| + + "ReplicaInputConnection..."); |
| + mInputMethodUma.recordProxyViewFailure(); |
| + ThreadUtils.postOnUiThread(new Runnable() { |
| + @Override |
| + public void run() { |
| + fallbackToReplicaInputConnection(view); |
| + } |
| + }); |
| + } else { |
| + // Most likely that we already lost view focus. |
| + mInputMethodUma.recordProxyViewDetectionFailure(); |
| + } |
| + } |
| + }); |
| + } |
| + }); |
| + } |
| + |
| + @VisibleForTesting |
| + protected void fallbackToReplicaInputConnection(final View view) { |
|
aelias_OOO_until_Jul13
2016/02/23 23:59:30
Because we plan to only ship ThreadedInputConnecti
Changwan Ryu
2016/02/24 00:43:29
Removed fallback mechanism and now we crash instea
|
| + // Disable IME thread until Chrome gets closed. Note that this is not a permanent |
| + // change. |
| + CommandLine.getInstance().appendSwitch(ContentSwitches.DISABLE_IME_THREAD); |
| + mImeAdapter.disableImeThread(); |
| + mImeAdapter.resetInputConnectionFactory(); |
| + mInputMethodManagerWrapper.restartInput(view); |
| + } |
| + |
| + @Override |
| + public Handler getHandler() { |
| + return mHandler; |
| + } |
| +} |