| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.content.browser.input; | 5 package org.chromium.content.browser.input; |
| 6 | 6 |
| 7 import android.os.Handler; | 7 import android.os.Handler; |
| 8 import android.os.HandlerThread; | 8 import android.os.HandlerThread; |
| 9 import android.view.View; | 9 import android.view.View; |
| 10 import android.view.inputmethod.EditorInfo; | 10 import android.view.inputmethod.EditorInfo; |
| 11 | 11 |
| 12 import org.chromium.base.Log; | 12 import org.chromium.base.Log; |
| 13 import org.chromium.base.VisibleForTesting; | 13 import org.chromium.base.VisibleForTesting; |
| 14 import org.chromium.base.annotations.SuppressFBWarnings; | |
| 15 | 14 |
| 16 /** | 15 /** |
| 17 * A factory class for {@link ThreadedInputConnection}. The class also includes
triggering | 16 * A factory class for {@link ThreadedInputConnection}. The class also includes
triggering |
| 18 * mechanism (hack) to run our InputConnection on non-UI thread. | 17 * mechanism (hack) to run our InputConnection on non-UI thread. |
| 19 */ | 18 */ |
| 20 // TODO(changwan): add unit tests once Robolectric supports Android API level >=
21. | 19 // TODO(changwan): add unit tests once Robolectric supports Android API level >=
21. |
| 21 // See crbug.com/588547 for details. | 20 // See crbug.com/588547 for details. |
| 22 public class ThreadedInputConnectionFactory implements ChromiumBaseInputConnecti
on.Factory { | 21 public class ThreadedInputConnectionFactory implements ChromiumBaseInputConnecti
on.Factory { |
| 23 private static final String TAG = "cr_Ime"; | 22 private static final String TAG = "cr_Ime"; |
| 24 private static final boolean DEBUG_LOGS = false; | 23 private static final boolean DEBUG_LOGS = false; |
| 25 | 24 |
| 26 // Most of the time we do not need to retry. But if we have lost window focu
s while triggering | 25 // Most of the time we do not need to retry. But if we have lost window focu
s while triggering |
| 27 // delayed creation, then there is a chance that detection may fail in the f
ollowing scenario: | 26 // delayed creation, then there is a chance that detection may fail in the f
ollowing scenario: |
| 28 // InputMethodManagerService checks the window focus by directly calling | 27 // InputMethodManagerService checks the window focus by directly calling |
| 29 // WindowManagerService#inputMethodClientHasFocus(). But the window focus ch
ange is | 28 // WindowManagerService#inputMethodClientHasFocus(). But the window focus ch
ange is |
| 30 // propagated to the view via ViewRootImpl's message queue. Therefore, it ma
y take another | 29 // propagated to the view via ViewRootImpl's message queue. Therefore, it ma
y take another |
| 31 // UI message loop until View#hasWindowFocus() is aligned with what IMMS see
s. | 30 // UI message loop until View#hasWindowFocus() is aligned with what IMMS see
s. |
| 32 private static final int CHECK_REGISTER_RETRY = 1; | 31 private static final int CHECK_REGISTER_RETRY = 1; |
| 33 | 32 |
| 34 // Reused for multiple WebView instances. TODO(changwan): check if we need t
o quit the loop | |
| 35 // for the last webview instance. | |
| 36 private static HandlerThread sHandlerThread; | |
| 37 | |
| 38 private final Handler mHandler; | |
| 39 private final InputMethodManagerWrapper mInputMethodManagerWrapper; | 33 private final InputMethodManagerWrapper mInputMethodManagerWrapper; |
| 40 private final InputMethodUma mInputMethodUma; | 34 private final InputMethodUma mInputMethodUma; |
| 41 private ThreadedInputConnectionProxyView mProxyView; | 35 private ThreadedInputConnectionProxyView mProxyView; |
| 42 private ThreadedInputConnection mThreadedInputConnection; | 36 private ThreadedInputConnection mThreadedInputConnection; |
| 43 private CheckInvalidator mCheckInvalidator; | 37 private CheckInvalidator mCheckInvalidator; |
| 44 private boolean mReentrantTriggering; | 38 private boolean mReentrantTriggering; |
| 45 | 39 |
| 40 // Initialization-on-demand holder for Handler. |
| 41 private static class LazyHandlerHolder { |
| 42 // Note that we never exit this thread to avoid lifetime or thread-safet
y issues. |
| 43 private static final Handler sHandler; |
| 44 static { |
| 45 HandlerThread handlerThread = |
| 46 new HandlerThread("InputConnectionHandlerThread", HandlerThr
ead.NORM_PRIORITY); |
| 47 handlerThread.start(); |
| 48 sHandler = new Handler(handlerThread.getLooper()); |
| 49 } |
| 50 } |
| 51 |
| 46 // A small class that can be updated to invalidate the check when there is a
n external event | 52 // A small class that can be updated to invalidate the check when there is a
n external event |
| 47 // such as window focus loss or view focus loss. | 53 // such as window focus loss or view focus loss. |
| 48 private static class CheckInvalidator { | 54 private static class CheckInvalidator { |
| 49 private boolean mInvalid; | 55 private boolean mInvalid; |
| 50 | 56 |
| 51 public void invalidate() { | 57 public void invalidate() { |
| 52 ImeUtils.checkOnUiThread(); | 58 ImeUtils.checkOnUiThread(); |
| 53 mInvalid = true; | 59 mInvalid = true; |
| 54 } | 60 } |
| 55 | 61 |
| 56 public boolean isInvalid() { | 62 public boolean isInvalid() { |
| 57 ImeUtils.checkOnUiThread(); | 63 ImeUtils.checkOnUiThread(); |
| 58 return mInvalid; | 64 return mInvalid; |
| 59 } | 65 } |
| 60 } | 66 } |
| 61 | 67 |
| 62 ThreadedInputConnectionFactory(InputMethodManagerWrapper inputMethodManagerW
rapper) { | 68 ThreadedInputConnectionFactory(InputMethodManagerWrapper inputMethodManagerW
rapper) { |
| 63 mInputMethodManagerWrapper = inputMethodManagerWrapper; | 69 mInputMethodManagerWrapper = inputMethodManagerWrapper; |
| 64 mHandler = createHandler(); | |
| 65 mInputMethodUma = createInputMethodUma(); | 70 mInputMethodUma = createInputMethodUma(); |
| 66 } | 71 } |
| 67 | 72 |
| 68 @VisibleForTesting | 73 @Override |
| 69 @SuppressFBWarnings("LI_LAZY_INIT_UPDATE_STATIC") | 74 public Handler getHandler() { |
| 70 protected Handler createHandler() { | 75 return LazyHandlerHolder.sHandler; |
| 71 if (sHandlerThread == null) { | |
| 72 sHandlerThread = | |
| 73 new HandlerThread("InputConnectionHandlerThread", HandlerThr
ead.NORM_PRIORITY); | |
| 74 sHandlerThread.start(); | |
| 75 } | |
| 76 return new Handler(sHandlerThread.getLooper()); | |
| 77 } | 76 } |
| 78 | 77 |
| 79 @VisibleForTesting | 78 @VisibleForTesting |
| 80 protected ThreadedInputConnectionProxyView createProxyView( | 79 protected ThreadedInputConnectionProxyView createProxyView( |
| 81 Handler handler, View containerView) { | 80 Handler handler, View containerView) { |
| 82 return new ThreadedInputConnectionProxyView( | 81 return new ThreadedInputConnectionProxyView( |
| 83 containerView.getContext(), handler, containerView); | 82 containerView.getContext(), handler, containerView); |
| 84 } | 83 } |
| 85 | 84 |
| 86 @VisibleForTesting | 85 @VisibleForTesting |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 126 if (mCheckInvalidator != null) mCheckInvalidator.invalidate(); | 125 if (mCheckInvalidator != null) mCheckInvalidator.invalidate(); |
| 127 | 126 |
| 128 if (shouldTriggerDelayedOnCreateInputConnection()) { | 127 if (shouldTriggerDelayedOnCreateInputConnection()) { |
| 129 triggerDelayedOnCreateInputConnection(view); | 128 triggerDelayedOnCreateInputConnection(view); |
| 130 return null; | 129 return null; |
| 131 } | 130 } |
| 132 if (DEBUG_LOGS) Log.w(TAG, "initializeAndGet: called from proxy view"); | 131 if (DEBUG_LOGS) Log.w(TAG, "initializeAndGet: called from proxy view"); |
| 133 | 132 |
| 134 if (mThreadedInputConnection == null) { | 133 if (mThreadedInputConnection == null) { |
| 135 if (DEBUG_LOGS) Log.w(TAG, "Creating ThreadedInputConnection..."); | 134 if (DEBUG_LOGS) Log.w(TAG, "Creating ThreadedInputConnection..."); |
| 136 mThreadedInputConnection = new ThreadedInputConnection(view, imeAdap
ter, mHandler); | 135 mThreadedInputConnection = new ThreadedInputConnection(view, imeAdap
ter, getHandler()); |
| 137 } else { | 136 } else { |
| 138 mThreadedInputConnection.resetOnUiThread(); | 137 mThreadedInputConnection.resetOnUiThread(); |
| 139 } | 138 } |
| 140 return mThreadedInputConnection; | 139 return mThreadedInputConnection; |
| 141 } | 140 } |
| 142 | 141 |
| 143 private void triggerDelayedOnCreateInputConnection(final View view) { | 142 private void triggerDelayedOnCreateInputConnection(final View view) { |
| 144 if (DEBUG_LOGS) Log.w(TAG, "triggerDelayedOnCreateInputConnection"); | 143 if (DEBUG_LOGS) Log.w(TAG, "triggerDelayedOnCreateInputConnection"); |
| 145 // Prevent infinite loop when View methods trigger onCreateInputConnecti
on | 144 // Prevent infinite loop when View methods trigger onCreateInputConnecti
on |
| 146 // on some OEM phones. (crbug.com/636197) | 145 // on some OEM phones. (crbug.com/636197) |
| 147 if (mReentrantTriggering) return; | 146 if (mReentrantTriggering) return; |
| 148 | 147 |
| 149 // We need to check this before creating invalidator. | 148 // We need to check this before creating invalidator. |
| 150 if (!view.hasFocus()) return; | 149 if (!view.hasFocus()) return; |
| 151 | 150 |
| 152 mCheckInvalidator = new CheckInvalidator(); | 151 mCheckInvalidator = new CheckInvalidator(); |
| 153 | 152 |
| 154 if (!view.hasWindowFocus()) mCheckInvalidator.invalidate(); | 153 if (!view.hasWindowFocus()) mCheckInvalidator.invalidate(); |
| 155 | 154 |
| 156 // We cannot reuse the existing proxy view, if any, due to crbug.com/664
402. | 155 // We cannot reuse the existing proxy view, if any, due to crbug.com/664
402. |
| 157 mProxyView = createProxyView(mHandler, view); | 156 mProxyView = createProxyView(getHandler(), view); |
| 158 | 157 |
| 159 mReentrantTriggering = true; | 158 mReentrantTriggering = true; |
| 160 // This does not affect view focus of the real views. | 159 // This does not affect view focus of the real views. |
| 161 mProxyView.requestFocus(); | 160 mProxyView.requestFocus(); |
| 162 mReentrantTriggering = false; | 161 mReentrantTriggering = false; |
| 163 | 162 |
| 164 view.getHandler().post(new Runnable() { | 163 view.getHandler().post(new Runnable() { |
| 165 @Override | 164 @Override |
| 166 public void run() { | 165 public void run() { |
| 167 // This is a hack to make InputMethodManager believe that the pr
oxy view | 166 // This is a hack to make InputMethodManager believe that the pr
oxy view |
| 168 // now has a focus. As a result, InputMethodManager will think t
hat mProxyView | 167 // now has a focus. As a result, InputMethodManager will think t
hat mProxyView |
| 169 // is focused, and will call getHandler() of the view when creat
ing input | 168 // is focused, and will call getHandler() of the view when creat
ing input |
| 170 // connection. | 169 // connection. |
| 171 | 170 |
| 172 // Step 1: Set mProxyView as InputMethodManager#mNextServedView. | 171 // Step 1: Set mProxyView as InputMethodManager#mNextServedView. |
| 173 // This does not affect the real window focus. | 172 // This does not affect the real window focus. |
| 174 mProxyView.onWindowFocusChanged(true); | 173 mProxyView.onWindowFocusChanged(true); |
| 175 | 174 |
| 176 // Step 2: Have InputMethodManager focus in on mNextServedView. | 175 // Step 2: Have InputMethodManager focus in on mNextServedView. |
| 177 // As a result, IMM will call onCreateInputConnection() on mProx
yView on the same | 176 // As a result, IMM will call onCreateInputConnection() on mProx
yView on the same |
| 178 // thread as mProxyView.getHandler(). It will also call subseque
nt InputConnection | 177 // thread as mProxyView.getHandler(). It will also call subseque
nt InputConnection |
| 179 // methods on this IME thread. | 178 // methods on this IME thread. |
| 180 mInputMethodManagerWrapper.isActive(view); | 179 mInputMethodManagerWrapper.isActive(view); |
| 181 | 180 |
| 182 // Step 3: Check that the above hack worked. | 181 // Step 3: Check that the above hack worked. |
| 183 // Do not check until activation finishes inside InputMethodMana
ger (on IME thread). | 182 // Do not check until activation finishes inside InputMethodMana
ger (on IME thread). |
| 184 mHandler.post(new Runnable() { | 183 getHandler().post(new Runnable() { |
| 185 @Override | 184 @Override |
| 186 public void run() { | 185 public void run() { |
| 187 postCheckRegisterResultOnUiThread(view, mCheckInvalidato
r, | 186 postCheckRegisterResultOnUiThread(view, mCheckInvalidato
r, |
| 188 CHECK_REGISTER_RETRY); | 187 CHECK_REGISTER_RETRY); |
| 189 } | 188 } |
| 190 }); | 189 }); |
| 191 } | 190 } |
| 192 }); | 191 }); |
| 193 } | 192 } |
| 194 | 193 |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 230 mInputMethodUma.recordProxyViewSuccess(); | 229 mInputMethodUma.recordProxyViewSuccess(); |
| 231 } | 230 } |
| 232 | 231 |
| 233 @VisibleForTesting | 232 @VisibleForTesting |
| 234 protected void onRegisterProxyViewFailure() { | 233 protected void onRegisterProxyViewFailure() { |
| 235 Log.w(TAG, "onRegisterProxyViewFailure"); | 234 Log.w(TAG, "onRegisterProxyViewFailure"); |
| 236 mInputMethodUma.recordProxyViewFailure(); | 235 mInputMethodUma.recordProxyViewFailure(); |
| 237 } | 236 } |
| 238 | 237 |
| 239 @Override | 238 @Override |
| 240 public Handler getHandler() { | |
| 241 return mHandler; | |
| 242 } | |
| 243 | |
| 244 @Override | |
| 245 public void onWindowFocusChanged(boolean gainFocus) { | 239 public void onWindowFocusChanged(boolean gainFocus) { |
| 246 if (DEBUG_LOGS) Log.d(TAG, "onWindowFocusChanged: " + gainFocus); | 240 if (DEBUG_LOGS) Log.d(TAG, "onWindowFocusChanged: " + gainFocus); |
| 247 if (!gainFocus && mCheckInvalidator != null) mCheckInvalidator.invalidat
e(); | 241 if (!gainFocus && mCheckInvalidator != null) mCheckInvalidator.invalidat
e(); |
| 248 if (mProxyView != null) mProxyView.onOriginalViewWindowFocusChanged(gain
Focus); | 242 if (mProxyView != null) mProxyView.onOriginalViewWindowFocusChanged(gain
Focus); |
| 249 } | 243 } |
| 250 | 244 |
| 251 @Override | 245 @Override |
| 252 public void onViewFocusChanged(boolean gainFocus) { | 246 public void onViewFocusChanged(boolean gainFocus) { |
| 253 if (DEBUG_LOGS) Log.d(TAG, "onViewFocusChanged: " + gainFocus); | 247 if (DEBUG_LOGS) Log.d(TAG, "onViewFocusChanged: " + gainFocus); |
| 254 if (!gainFocus && mCheckInvalidator != null) mCheckInvalidator.invalidat
e(); | 248 if (!gainFocus && mCheckInvalidator != null) mCheckInvalidator.invalidat
e(); |
| 255 if (mProxyView != null) mProxyView.onOriginalViewFocusChanged(gainFocus)
; | 249 if (mProxyView != null) mProxyView.onOriginalViewFocusChanged(gainFocus)
; |
| 256 } | 250 } |
| 257 | 251 |
| 258 @Override | 252 @Override |
| 259 public void onViewAttachedToWindow() { | 253 public void onViewAttachedToWindow() { |
| 260 if (DEBUG_LOGS) Log.d(TAG, "onViewAttachedToWindow"); | 254 if (DEBUG_LOGS) Log.d(TAG, "onViewAttachedToWindow"); |
| 261 if (mProxyView != null) mProxyView.onOriginalViewAttachedToWindow(); | 255 if (mProxyView != null) mProxyView.onOriginalViewAttachedToWindow(); |
| 262 } | 256 } |
| 263 | 257 |
| 264 @Override | 258 @Override |
| 265 public void onViewDetachedFromWindow() { | 259 public void onViewDetachedFromWindow() { |
| 266 if (DEBUG_LOGS) Log.d(TAG, "onViewDetachedFromWindow"); | 260 if (DEBUG_LOGS) Log.d(TAG, "onViewDetachedFromWindow"); |
| 267 if (mCheckInvalidator != null) mCheckInvalidator.invalidate(); | 261 if (mCheckInvalidator != null) mCheckInvalidator.invalidate(); |
| 268 if (mProxyView != null) mProxyView.onOriginalViewDetachedFromWindow(); | 262 if (mProxyView != null) mProxyView.onOriginalViewDetachedFromWindow(); |
| 269 } | 263 } |
| 270 } | 264 } |
| OLD | NEW |