| 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.ThreadUtils; |
| 13 import org.chromium.base.VisibleForTesting; | 14 import org.chromium.base.VisibleForTesting; |
| 14 | 15 |
| 15 /** | 16 /** |
| 16 * A factory class for {@link ThreadedInputConnection}. The class also includes
triggering | 17 * A factory class for {@link ThreadedInputConnection}. The class also includes
triggering |
| 17 * mechanism (hack) to run our InputConnection on non-UI thread. | 18 * mechanism (hack) to run our InputConnection on non-UI thread. |
| 18 */ | 19 */ |
| 19 // TODO(changwan): add unit tests once Robolectric supports Android API level >=
21. | 20 // TODO(changwan): add unit tests once Robolectric supports Android API level >=
21. |
| 20 // See crbug.com/588547 for details. | 21 // See crbug.com/588547 for details. |
| 21 public class ThreadedInputConnectionFactory implements ChromiumBaseInputConnecti
on.Factory { | 22 public class ThreadedInputConnectionFactory implements ChromiumBaseInputConnecti
on.Factory { |
| 22 private static final String TAG = "cr_Ime"; | 23 private static final String TAG = "cr_Ime"; |
| 23 private static final boolean DEBUG_LOGS = false; | 24 private static final boolean DEBUG_LOGS = false; |
| 24 | 25 |
| 25 // Most of the time we do not need to retry. But if we have lost window focu
s while triggering | 26 // Most of the time we do not need to retry. But if we have lost window focu
s while triggering |
| 26 // delayed creation, then there is a chance that detection may fail in the f
ollowing scenario: | 27 // delayed creation, then there is a chance that detection may fail in the f
ollowing scenario: |
| 27 // InputMethodManagerService checks the window focus by directly calling | 28 // InputMethodManagerService checks the window focus by directly calling |
| 28 // WindowManagerService#inputMethodClientHasFocus(). But the window focus ch
ange is | 29 // WindowManagerService#inputMethodClientHasFocus(). But the window focus ch
ange is |
| 29 // propagated to the view via ViewRootImpl's message queue. Therefore, it ma
y take another | 30 // propagated to the view via ViewRootImpl's message queue. Therefore, it ma
y take another |
| 30 // UI message loop until View#hasWindowFocus() is aligned with what IMMS see
s. | 31 // UI message loop until View#hasWindowFocus() is aligned with what IMMS see
s. |
| 31 private static final int CHECK_REGISTER_RETRY = 1; | 32 private static final int CHECK_REGISTER_RETRY = 1; |
| 32 | 33 |
| 33 private final Handler mHandler; | 34 private Handler mHandler; |
| 34 private final InputMethodManagerWrapper mInputMethodManagerWrapper; | 35 private final InputMethodManagerWrapper mInputMethodManagerWrapper; |
| 35 private final InputMethodUma mInputMethodUma; | 36 private final InputMethodUma mInputMethodUma; |
| 36 private ThreadedInputConnectionProxyView mProxyView; | 37 private ThreadedInputConnectionProxyView mProxyView; |
| 37 private ThreadedInputConnection mThreadedInputConnection; | 38 private ThreadedInputConnection mThreadedInputConnection; |
| 38 private CheckInvalidator mCheckInvalidator; | 39 private CheckInvalidator mCheckInvalidator; |
| 39 private boolean mReentrantTriggering; | 40 private boolean mReentrantTriggering; |
| 40 | 41 |
| 41 // A small class that can be updated to invalidate the check when there is a
n external event | 42 // A small class that can be updated to invalidate the check when there is a
n external event |
| 42 // such as window focus loss or view focus loss. | 43 // such as window focus loss or view focus loss. |
| 43 private static class CheckInvalidator { | 44 private static class CheckInvalidator { |
| 44 private boolean mInvalid; | 45 private boolean mInvalid; |
| 45 | 46 |
| 46 public void invalidate() { | 47 public void invalidate() { |
| 47 ImeUtils.checkOnUiThread(); | 48 ImeUtils.checkOnUiThread(); |
| 48 mInvalid = true; | 49 mInvalid = true; |
| 49 } | 50 } |
| 50 | 51 |
| 51 public boolean isInvalid() { | 52 public boolean isInvalid() { |
| 52 ImeUtils.checkOnUiThread(); | 53 ImeUtils.checkOnUiThread(); |
| 53 return mInvalid; | 54 return mInvalid; |
| 54 } | 55 } |
| 55 } | 56 } |
| 56 | 57 |
| 57 ThreadedInputConnectionFactory(InputMethodManagerWrapper inputMethodManagerW
rapper) { | 58 ThreadedInputConnectionFactory(InputMethodManagerWrapper inputMethodManagerW
rapper) { |
| 58 mInputMethodManagerWrapper = inputMethodManagerWrapper; | 59 mInputMethodManagerWrapper = inputMethodManagerWrapper; |
| 59 mHandler = createHandler(); | |
| 60 mInputMethodUma = createInputMethodUma(); | 60 mInputMethodUma = createInputMethodUma(); |
| 61 } | 61 } |
| 62 | 62 |
| 63 @VisibleForTesting | 63 @VisibleForTesting |
| 64 protected Handler createHandler() { | 64 protected Handler createHandler() { |
| 65 HandlerThread thread = | 65 HandlerThread thread = |
| 66 new HandlerThread("InputConnectionHandlerThread", HandlerThread.
NORM_PRIORITY); | 66 new HandlerThread("InputConnectionHandlerThread", HandlerThread.
NORM_PRIORITY); |
| 67 thread.start(); | 67 thread.start(); |
| 68 return new Handler(thread.getLooper()); | 68 return new Handler(thread.getLooper()); |
| 69 } | 69 } |
| 70 | 70 |
| 71 private void destroyHandler() { |
| 72 if (mHandler == null) return; |
| 73 final Handler handler = mHandler; |
| 74 handler.post(new Runnable() { |
| 75 @Override |
| 76 public void run() { |
| 77 ThreadUtils.postOnUiThread(new Runnable() { |
| 78 @Override |
| 79 public void run() { |
| 80 if (handler != null) { |
| 81 handler.getLooper().quit(); |
| 82 } |
| 83 } |
| 84 }); |
| 85 } |
| 86 }); |
| 87 mHandler = null; |
| 88 } |
| 89 |
| 71 @VisibleForTesting | 90 @VisibleForTesting |
| 72 protected ThreadedInputConnectionProxyView createProxyView( | 91 protected ThreadedInputConnectionProxyView createProxyView( |
| 73 Handler handler, View containerView) { | 92 Handler handler, View containerView) { |
| 74 return new ThreadedInputConnectionProxyView( | 93 return new ThreadedInputConnectionProxyView( |
| 75 containerView.getContext(), handler, containerView); | 94 containerView.getContext(), handler, containerView); |
| 76 } | 95 } |
| 77 | 96 |
| 78 @VisibleForTesting | 97 @VisibleForTesting |
| 79 protected InputMethodUma createInputMethodUma() { | 98 protected InputMethodUma createInputMethodUma() { |
| 80 return new InputMethodUma(); | 99 return new InputMethodUma(); |
| (...skipping 29 matching lines...) Expand all Loading... |
| 110 ImeUtils.computeEditorInfo( | 129 ImeUtils.computeEditorInfo( |
| 111 inputType, inputFlags, inputMode, selectionStart, selectionEnd,
outAttrs); | 130 inputType, inputFlags, inputMode, selectionStart, selectionEnd,
outAttrs); |
| 112 if (DEBUG_LOGS) { | 131 if (DEBUG_LOGS) { |
| 113 Log.w(TAG, "initializeAndGet. outAttr: " + ImeUtils.getEditorInfoDeb
ugString(outAttrs)); | 132 Log.w(TAG, "initializeAndGet. outAttr: " + ImeUtils.getEditorInfoDeb
ugString(outAttrs)); |
| 114 } | 133 } |
| 115 | 134 |
| 116 // IMM can internally ignore subsequent activation requests, e.g., by ch
ecking | 135 // IMM can internally ignore subsequent activation requests, e.g., by ch
ecking |
| 117 // mServedConnecting. | 136 // mServedConnecting. |
| 118 if (mCheckInvalidator != null) mCheckInvalidator.invalidate(); | 137 if (mCheckInvalidator != null) mCheckInvalidator.invalidate(); |
| 119 | 138 |
| 139 if (mHandler == null) mHandler = createHandler(); |
| 140 |
| 120 if (shouldTriggerDelayedOnCreateInputConnection()) { | 141 if (shouldTriggerDelayedOnCreateInputConnection()) { |
| 121 triggerDelayedOnCreateInputConnection(view); | 142 triggerDelayedOnCreateInputConnection(view); |
| 122 return null; | 143 return null; |
| 123 } | 144 } |
| 124 if (DEBUG_LOGS) Log.w(TAG, "initializeAndGet: called from proxy view"); | 145 if (DEBUG_LOGS) Log.w(TAG, "initializeAndGet: called from proxy view"); |
| 125 | 146 |
| 126 if (mThreadedInputConnection == null) { | 147 if (mThreadedInputConnection == null) { |
| 127 if (DEBUG_LOGS) Log.w(TAG, "Creating ThreadedInputConnection..."); | 148 if (DEBUG_LOGS) Log.w(TAG, "Creating ThreadedInputConnection..."); |
| 128 mThreadedInputConnection = new ThreadedInputConnection(view, imeAdap
ter, mHandler); | 149 mThreadedInputConnection = new ThreadedInputConnection(view, imeAdap
ter, mHandler); |
| 129 } else { | 150 } else { |
| 130 mThreadedInputConnection.resetOnUiThread(); | 151 mThreadedInputConnection.resetOnUiThread(); |
| 131 } | 152 } |
| 132 return mThreadedInputConnection; | 153 return mThreadedInputConnection; |
| 133 } | 154 } |
| 134 | 155 |
| 135 private void triggerDelayedOnCreateInputConnection(final View view) { | 156 private void triggerDelayedOnCreateInputConnection(final View view) { |
| 136 if (DEBUG_LOGS) Log.w(TAG, "triggerDelayedOnCreateInputConnection"); | 157 if (DEBUG_LOGS) Log.w(TAG, "triggerDelayedOnCreateInputConnection"); |
| 137 // Prevent infinite loop when View methods trigger onCreateInputConnecti
on | 158 // Prevent infinite loop when View methods trigger onCreateInputConnecti
on |
| 138 // on some OEM phones. (crbug.com/636197) | 159 // on some OEM phones. (crbug.com/636197) |
| 139 if (mReentrantTriggering) return; | 160 if (mReentrantTriggering) return; |
| 140 | 161 |
| 141 // We need to check this before creating invalidator. | 162 // We need to check this before creating invalidator. |
| 142 if (!view.hasFocus()) return; | 163 if (!view.hasFocus()) return; |
| 143 | 164 |
| 144 mCheckInvalidator = new CheckInvalidator(); | 165 mCheckInvalidator = new CheckInvalidator(); |
| 145 | 166 |
| 146 if (!view.hasWindowFocus()) mCheckInvalidator.invalidate(); | 167 if (!view.hasWindowFocus()) mCheckInvalidator.invalidate(); |
| 147 | 168 |
| 169 if (mHandler == null) return; |
| 148 // We cannot reuse the existing proxy view, if any, due to crbug.com/664
402. | 170 // We cannot reuse the existing proxy view, if any, due to crbug.com/664
402. |
| 149 mProxyView = createProxyView(mHandler, view); | 171 mProxyView = createProxyView(mHandler, view); |
| 150 | 172 |
| 151 mReentrantTriggering = true; | 173 mReentrantTriggering = true; |
| 152 // This does not affect view focus of the real views. | 174 // This does not affect view focus of the real views. |
| 153 mProxyView.requestFocus(); | 175 mProxyView.requestFocus(); |
| 154 mReentrantTriggering = false; | 176 mReentrantTriggering = false; |
| 155 | 177 |
| 156 view.getHandler().post(new Runnable() { | 178 view.getHandler().post(new Runnable() { |
| 157 @Override | 179 @Override |
| 158 public void run() { | 180 public void run() { |
| 159 // This is a hack to make InputMethodManager believe that the pr
oxy view | 181 // This is a hack to make InputMethodManager believe that the pr
oxy view |
| 160 // now has a focus. As a result, InputMethodManager will think t
hat mProxyView | 182 // now has a focus. As a result, InputMethodManager will think t
hat mProxyView |
| 161 // is focused, and will call getHandler() of the view when creat
ing input | 183 // is focused, and will call getHandler() of the view when creat
ing input |
| 162 // connection. | 184 // connection. |
| 163 | 185 |
| 164 // Step 1: Set mProxyView as InputMethodManager#mNextServedView. | 186 // Step 1: Set mProxyView as InputMethodManager#mNextServedView. |
| 165 // This does not affect the real window focus. | 187 // This does not affect the real window focus. |
| 166 mProxyView.onWindowFocusChanged(true); | 188 mProxyView.onWindowFocusChanged(true); |
| 167 | 189 |
| 168 // Step 2: Have InputMethodManager focus in on mNextServedView. | 190 // Step 2: Have InputMethodManager focus in on mNextServedView. |
| 169 // As a result, IMM will call onCreateInputConnection() on mProx
yView on the same | 191 // As a result, IMM will call onCreateInputConnection() on mProx
yView on the same |
| 170 // thread as mProxyView.getHandler(). It will also call subseque
nt InputConnection | 192 // thread as mProxyView.getHandler(). It will also call subseque
nt InputConnection |
| 171 // methods on this IME thread. | 193 // methods on this IME thread. |
| 172 mInputMethodManagerWrapper.isActive(view); | 194 mInputMethodManagerWrapper.isActive(view); |
| 173 | 195 |
| 174 // Step 3: Check that the above hack worked. | 196 // Step 3: Check that the above hack worked. |
| 175 // Do not check until activation finishes inside InputMethodMana
ger (on IME thread). | 197 // Do not check until activation finishes inside InputMethodMana
ger (on IME thread). |
| 198 if (mHandler == null) return; |
| 176 mHandler.post(new Runnable() { | 199 mHandler.post(new Runnable() { |
| 177 @Override | 200 @Override |
| 178 public void run() { | 201 public void run() { |
| 179 postCheckRegisterResultOnUiThread(view, mCheckInvalidato
r, | 202 postCheckRegisterResultOnUiThread(view, mCheckInvalidato
r, |
| 180 CHECK_REGISTER_RETRY); | 203 CHECK_REGISTER_RETRY); |
| 181 } | 204 } |
| 182 }); | 205 }); |
| 183 } | 206 } |
| 184 }); | 207 }); |
| 185 } | 208 } |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 244 public void onViewFocusChanged(boolean gainFocus) { | 267 public void onViewFocusChanged(boolean gainFocus) { |
| 245 if (DEBUG_LOGS) Log.d(TAG, "onViewFocusChanged: " + gainFocus); | 268 if (DEBUG_LOGS) Log.d(TAG, "onViewFocusChanged: " + gainFocus); |
| 246 if (!gainFocus && mCheckInvalidator != null) mCheckInvalidator.invalidat
e(); | 269 if (!gainFocus && mCheckInvalidator != null) mCheckInvalidator.invalidat
e(); |
| 247 if (mProxyView != null) mProxyView.onOriginalViewFocusChanged(gainFocus)
; | 270 if (mProxyView != null) mProxyView.onOriginalViewFocusChanged(gainFocus)
; |
| 248 } | 271 } |
| 249 | 272 |
| 250 @Override | 273 @Override |
| 251 public void onViewAttachedToWindow() { | 274 public void onViewAttachedToWindow() { |
| 252 if (DEBUG_LOGS) Log.d(TAG, "onViewAttachedToWindow"); | 275 if (DEBUG_LOGS) Log.d(TAG, "onViewAttachedToWindow"); |
| 253 if (mProxyView != null) mProxyView.onOriginalViewAttachedToWindow(); | 276 if (mProxyView != null) mProxyView.onOriginalViewAttachedToWindow(); |
| 277 if (mHandler == null) mHandler = createHandler(); |
| 254 } | 278 } |
| 255 | 279 |
| 256 @Override | 280 @Override |
| 257 public void onViewDetachedFromWindow() { | 281 public void onViewDetachedFromWindow() { |
| 258 if (DEBUG_LOGS) Log.d(TAG, "onViewDetachedFromWindow"); | 282 if (DEBUG_LOGS) Log.d(TAG, "onViewDetachedFromWindow"); |
| 259 if (mCheckInvalidator != null) mCheckInvalidator.invalidate(); | 283 if (mCheckInvalidator != null) mCheckInvalidator.invalidate(); |
| 260 if (mProxyView != null) mProxyView.onOriginalViewDetachedFromWindow(); | 284 if (mProxyView != null) mProxyView.onOriginalViewDetachedFromWindow(); |
| 285 destroyHandler(); |
| 286 } |
| 287 |
| 288 @Override |
| 289 public void destroy() { |
| 290 if (mCheckInvalidator != null) mCheckInvalidator.invalidate(); |
| 291 destroyHandler(); |
| 261 } | 292 } |
| 262 } | 293 } |
| OLD | NEW |