Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1748)

Unified Diff: content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionFactory.java

Issue 1278593004: Introduce ThreadedInputConnection behind a switch (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: removed ImeTest#testDoesNotHang_rendererCrashes which does not test anything Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..b79340864c28d8e2f3fbe2ea6e70aeed498e6139
--- /dev/null
+++ b/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionFactory.java
@@ -0,0 +1,156 @@
+// 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.Log;
+import org.chromium.base.VisibleForTesting;
+
+/**
+ * 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 InputMethodUma mInputMethodUma;
+ private ThreadedInputConnectionProxyView mProxyView;
+ private ThreadedInputConnection mThreadedInputConnection;
+
+ ThreadedInputConnectionFactory(
+ InputMethodManagerWrapper inputMethodManagerWrapper) {
+ 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() {
+ // Note that ThreadedInputConnectionProxyView intentionally calls
+ // View#onCreateInputConnection() and not a separate method in this class.
+ // There are third party apps that override WebView#onCreateInputConnection(),
+ // and we still want to call them for consistency. The setback here is that the only
+ // way to distinguish calls from InputMethodManager and from ProxyView is by looking at
+ // the call stack.
+ 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.
+ mInputMethodUma.recordProxyViewFailure();
+ onRegisterProxyViewFailed();
+ } else {
+ // Most likely that we already lost view focus.
+ mInputMethodUma.recordProxyViewDetectionFailure();
+ }
+ }
+ });
+ }
+ });
+ }
+
+ @VisibleForTesting
+ protected void onRegisterProxyViewFailed() {
+ throw new AssertionError("Failed to register proxy view");
+ }
+
+ @Override
+ public Handler getHandler() {
+ return mHandler;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698