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 |