OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.content.browser.input; |
| 6 |
| 7 import android.os.Handler; |
| 8 import android.os.HandlerThread; |
| 9 import android.view.View; |
| 10 import android.view.inputmethod.EditorInfo; |
| 11 |
| 12 import org.chromium.base.Log; |
| 13 import org.chromium.base.VisibleForTesting; |
| 14 |
| 15 /** |
| 16 * A factory class for {@link ThreadedInputConnection}. The class also includes
triggering |
| 17 * mechanism (hack) to run our InputConnection on non-UI thread. |
| 18 */ |
| 19 // TODO(changwan): add unit tests once Robolectric supports Android API level >=
21. |
| 20 // See crbug.com/588547 for details. |
| 21 public class ThreadedInputConnectionFactory implements ChromiumBaseInputConnecti
on.Factory { |
| 22 private static final String TAG = "cr_Ime"; |
| 23 private static final boolean DEBUG_LOGS = false; |
| 24 |
| 25 private final Handler mHandler; |
| 26 private final InputMethodManagerWrapper mInputMethodManagerWrapper; |
| 27 private final InputMethodUma mInputMethodUma; |
| 28 private ThreadedInputConnectionProxyView mProxyView; |
| 29 private ThreadedInputConnection mThreadedInputConnection; |
| 30 |
| 31 ThreadedInputConnectionFactory( |
| 32 InputMethodManagerWrapper inputMethodManagerWrapper) { |
| 33 mInputMethodManagerWrapper = inputMethodManagerWrapper; |
| 34 mHandler = createHandler(); |
| 35 mInputMethodUma = createInputMethodUma(); |
| 36 } |
| 37 |
| 38 @VisibleForTesting |
| 39 protected Handler createHandler() { |
| 40 HandlerThread thread = |
| 41 new HandlerThread("InputConnectionHandlerThread", HandlerThread.
NORM_PRIORITY); |
| 42 thread.start(); |
| 43 return new Handler(thread.getLooper()); |
| 44 } |
| 45 |
| 46 @VisibleForTesting |
| 47 protected ThreadedInputConnectionProxyView createProxyView( |
| 48 Handler handler, View containerView) { |
| 49 return new ThreadedInputConnectionProxyView( |
| 50 containerView.getContext(), handler, containerView); |
| 51 } |
| 52 |
| 53 @VisibleForTesting |
| 54 protected InputMethodUma createInputMethodUma() { |
| 55 return new InputMethodUma(); |
| 56 } |
| 57 |
| 58 private boolean shouldTriggerDelayedOnCreateInputConnection() { |
| 59 // Note that ThreadedInputConnectionProxyView intentionally calls |
| 60 // View#onCreateInputConnection() and not a separate method in this clas
s. |
| 61 // There are third party apps that override WebView#onCreateInputConnect
ion(), |
| 62 // and we still want to call them for consistency. The setback here is t
hat the only |
| 63 // way to distinguish calls from InputMethodManager and from ProxyView i
s by looking at |
| 64 // the call stack. |
| 65 for (StackTraceElement ste : Thread.currentThread().getStackTrace()) { |
| 66 String className = ste.getClassName(); |
| 67 if (className != null |
| 68 && (className.contains(ThreadedInputConnectionProxyView.clas
s.getName()) |
| 69 || className.contains("TestInputMethodManagerWrapper"))) { |
| 70 return false; |
| 71 } |
| 72 } |
| 73 return true; |
| 74 } |
| 75 |
| 76 @Override |
| 77 public ThreadedInputConnection initializeAndGet( |
| 78 View view, ImeAdapter imeAdapter, int inputType, int inputFlags, int
selectionStart, |
| 79 int selectionEnd, EditorInfo outAttrs) { |
| 80 ImeUtils.checkOnUiThread(); |
| 81 if (shouldTriggerDelayedOnCreateInputConnection()) { |
| 82 triggerDelayedOnCreateInputConnection(view); |
| 83 return null; |
| 84 } |
| 85 if (DEBUG_LOGS) Log.w(TAG, "initializeAndGet: called from proxy view"); |
| 86 if (mThreadedInputConnection == null) { |
| 87 if (DEBUG_LOGS) Log.w(TAG, "Creating ThreadedInputConnection..."); |
| 88 mThreadedInputConnection = new ThreadedInputConnection(imeAdapter, m
Handler); |
| 89 } |
| 90 mThreadedInputConnection.initializeOutAttrsOnUiThread(inputType, inputFl
ags, |
| 91 selectionStart, selectionEnd, outAttrs); |
| 92 return mThreadedInputConnection; |
| 93 } |
| 94 |
| 95 private void triggerDelayedOnCreateInputConnection(final View view) { |
| 96 if (DEBUG_LOGS) Log.w(TAG, "triggerDelayedOnCreateInputConnection"); |
| 97 if (mProxyView == null) { |
| 98 mProxyView = createProxyView(mHandler, view); |
| 99 } |
| 100 mProxyView.requestFocus(); |
| 101 view.getHandler().post(new Runnable() { |
| 102 @Override |
| 103 public void run() { |
| 104 // This is a hack to make InputMethodManager believe that the pr
oxy view |
| 105 // now has a focus. As a result, InputMethodManager will think t
hat mProxyView |
| 106 // is focused, and will call getHandler() of the view when creat
ing input |
| 107 // connection. |
| 108 |
| 109 // Step 1: Set mProxyView as InputMethodManager#mNextServedView. |
| 110 mProxyView.onWindowFocusChanged(true); |
| 111 |
| 112 // Step 2: Have InputMethodManager focus in on mNextServedView. |
| 113 // As a result, IMM will call onCreateInputConnection() on mProx
yView on the same |
| 114 // thread as mProxyView.getHandler(). It will also call subseque
nt InputConnection |
| 115 // methods on this IME thread. |
| 116 mInputMethodManagerWrapper.isActive(view); |
| 117 |
| 118 // Step 3: Check that the above hack worked. |
| 119 mHandler.post(new Runnable() { |
| 120 @Override |
| 121 public void run() { |
| 122 // Some other view already took focus. Container view sh
ould be active |
| 123 // otherwise regardless of whether proxy view is registe
red or not. |
| 124 if (!mInputMethodManagerWrapper.isActive(view)) return; |
| 125 |
| 126 // Success. |
| 127 if (mInputMethodManagerWrapper.isActive(mProxyView)) { |
| 128 mInputMethodUma.recordProxyViewSuccess(); |
| 129 return; |
| 130 } |
| 131 |
| 132 if (mThreadedInputConnection == null) { |
| 133 // First time and failed. It is highly likely that t
his does not work |
| 134 // systematically. |
| 135 mInputMethodUma.recordProxyViewFailure(); |
| 136 onRegisterProxyViewFailed(); |
| 137 } else { |
| 138 // Most likely that we already lost view focus. |
| 139 mInputMethodUma.recordProxyViewDetectionFailure(); |
| 140 } |
| 141 } |
| 142 }); |
| 143 } |
| 144 }); |
| 145 } |
| 146 |
| 147 @VisibleForTesting |
| 148 protected void onRegisterProxyViewFailed() { |
| 149 throw new AssertionError("Failed to register proxy view"); |
| 150 } |
| 151 |
| 152 @Override |
| 153 public Handler getHandler() { |
| 154 return mHandler; |
| 155 } |
| 156 } |
OLD | NEW |