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; | |
14 import org.chromium.base.VisibleForTesting; | 13 import org.chromium.base.VisibleForTesting; |
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 private Handler mHandler; | 33 private final Handler mHandler; |
35 private final InputMethodManagerWrapper mInputMethodManagerWrapper; | 34 private final InputMethodManagerWrapper mInputMethodManagerWrapper; |
36 private final InputMethodUma mInputMethodUma; | 35 private final InputMethodUma mInputMethodUma; |
37 private ThreadedInputConnectionProxyView mProxyView; | 36 private ThreadedInputConnectionProxyView mProxyView; |
38 private ThreadedInputConnection mThreadedInputConnection; | 37 private ThreadedInputConnection mThreadedInputConnection; |
39 private CheckInvalidator mCheckInvalidator; | 38 private CheckInvalidator mCheckInvalidator; |
40 private boolean mReentrantTriggering; | 39 private boolean mReentrantTriggering; |
41 | 40 |
42 // A small class that can be updated to invalidate the check when there is a
n external event | 41 // A small class that can be updated to invalidate the check when there is a
n external event |
43 // such as window focus loss or view focus loss. | 42 // such as window focus loss or view focus loss. |
44 private static class CheckInvalidator { | 43 private static class CheckInvalidator { |
45 private boolean mInvalid; | 44 private boolean mInvalid; |
46 | 45 |
47 public void invalidate() { | 46 public void invalidate() { |
48 ImeUtils.checkOnUiThread(); | 47 ImeUtils.checkOnUiThread(); |
49 mInvalid = true; | 48 mInvalid = true; |
50 } | 49 } |
51 | 50 |
52 public boolean isInvalid() { | 51 public boolean isInvalid() { |
53 ImeUtils.checkOnUiThread(); | 52 ImeUtils.checkOnUiThread(); |
54 return mInvalid; | 53 return mInvalid; |
55 } | 54 } |
56 } | 55 } |
57 | 56 |
58 ThreadedInputConnectionFactory(InputMethodManagerWrapper inputMethodManagerW
rapper) { | 57 ThreadedInputConnectionFactory(InputMethodManagerWrapper inputMethodManagerW
rapper) { |
59 mInputMethodManagerWrapper = inputMethodManagerWrapper; | 58 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 | |
90 @VisibleForTesting | 71 @VisibleForTesting |
91 protected ThreadedInputConnectionProxyView createProxyView( | 72 protected ThreadedInputConnectionProxyView createProxyView( |
92 Handler handler, View containerView) { | 73 Handler handler, View containerView) { |
93 return new ThreadedInputConnectionProxyView( | 74 return new ThreadedInputConnectionProxyView( |
94 containerView.getContext(), handler, containerView); | 75 containerView.getContext(), handler, containerView); |
95 } | 76 } |
96 | 77 |
97 @VisibleForTesting | 78 @VisibleForTesting |
98 protected InputMethodUma createInputMethodUma() { | 79 protected InputMethodUma createInputMethodUma() { |
99 return new InputMethodUma(); | 80 return new InputMethodUma(); |
(...skipping 29 matching lines...) Expand all Loading... |
129 ImeUtils.computeEditorInfo( | 110 ImeUtils.computeEditorInfo( |
130 inputType, inputFlags, inputMode, selectionStart, selectionEnd,
outAttrs); | 111 inputType, inputFlags, inputMode, selectionStart, selectionEnd,
outAttrs); |
131 if (DEBUG_LOGS) { | 112 if (DEBUG_LOGS) { |
132 Log.w(TAG, "initializeAndGet. outAttr: " + ImeUtils.getEditorInfoDeb
ugString(outAttrs)); | 113 Log.w(TAG, "initializeAndGet. outAttr: " + ImeUtils.getEditorInfoDeb
ugString(outAttrs)); |
133 } | 114 } |
134 | 115 |
135 // IMM can internally ignore subsequent activation requests, e.g., by ch
ecking | 116 // IMM can internally ignore subsequent activation requests, e.g., by ch
ecking |
136 // mServedConnecting. | 117 // mServedConnecting. |
137 if (mCheckInvalidator != null) mCheckInvalidator.invalidate(); | 118 if (mCheckInvalidator != null) mCheckInvalidator.invalidate(); |
138 | 119 |
139 if (mHandler == null) mHandler = createHandler(); | |
140 | |
141 if (shouldTriggerDelayedOnCreateInputConnection()) { | 120 if (shouldTriggerDelayedOnCreateInputConnection()) { |
142 triggerDelayedOnCreateInputConnection(view); | 121 triggerDelayedOnCreateInputConnection(view); |
143 return null; | 122 return null; |
144 } | 123 } |
145 if (DEBUG_LOGS) Log.w(TAG, "initializeAndGet: called from proxy view"); | 124 if (DEBUG_LOGS) Log.w(TAG, "initializeAndGet: called from proxy view"); |
146 | 125 |
147 if (mThreadedInputConnection == null) { | 126 if (mThreadedInputConnection == null) { |
148 if (DEBUG_LOGS) Log.w(TAG, "Creating ThreadedInputConnection..."); | 127 if (DEBUG_LOGS) Log.w(TAG, "Creating ThreadedInputConnection..."); |
149 mThreadedInputConnection = new ThreadedInputConnection(view, imeAdap
ter, mHandler); | 128 mThreadedInputConnection = new ThreadedInputConnection(view, imeAdap
ter, mHandler); |
150 } else { | 129 } else { |
151 mThreadedInputConnection.resetOnUiThread(); | 130 mThreadedInputConnection.resetOnUiThread(); |
152 } | 131 } |
153 return mThreadedInputConnection; | 132 return mThreadedInputConnection; |
154 } | 133 } |
155 | 134 |
156 private void triggerDelayedOnCreateInputConnection(final View view) { | 135 private void triggerDelayedOnCreateInputConnection(final View view) { |
157 if (DEBUG_LOGS) Log.w(TAG, "triggerDelayedOnCreateInputConnection"); | 136 if (DEBUG_LOGS) Log.w(TAG, "triggerDelayedOnCreateInputConnection"); |
158 // Prevent infinite loop when View methods trigger onCreateInputConnecti
on | 137 // Prevent infinite loop when View methods trigger onCreateInputConnecti
on |
159 // on some OEM phones. (crbug.com/636197) | 138 // on some OEM phones. (crbug.com/636197) |
160 if (mReentrantTriggering) return; | 139 if (mReentrantTriggering) return; |
161 | 140 |
162 // We need to check this before creating invalidator. | 141 // We need to check this before creating invalidator. |
163 if (!view.hasFocus()) return; | 142 if (!view.hasFocus()) return; |
164 | 143 |
165 mCheckInvalidator = new CheckInvalidator(); | 144 mCheckInvalidator = new CheckInvalidator(); |
166 | 145 |
167 if (!view.hasWindowFocus()) mCheckInvalidator.invalidate(); | 146 if (!view.hasWindowFocus()) mCheckInvalidator.invalidate(); |
168 | 147 |
169 if (mHandler == null) return; | |
170 // We cannot reuse the existing proxy view, if any, due to crbug.com/664
402. | 148 // We cannot reuse the existing proxy view, if any, due to crbug.com/664
402. |
171 mProxyView = createProxyView(mHandler, view); | 149 mProxyView = createProxyView(mHandler, view); |
172 | 150 |
173 mReentrantTriggering = true; | 151 mReentrantTriggering = true; |
174 // This does not affect view focus of the real views. | 152 // This does not affect view focus of the real views. |
175 mProxyView.requestFocus(); | 153 mProxyView.requestFocus(); |
176 mReentrantTriggering = false; | 154 mReentrantTriggering = false; |
177 | 155 |
178 view.getHandler().post(new Runnable() { | 156 view.getHandler().post(new Runnable() { |
179 @Override | 157 @Override |
180 public void run() { | 158 public void run() { |
181 // This is a hack to make InputMethodManager believe that the pr
oxy view | 159 // This is a hack to make InputMethodManager believe that the pr
oxy view |
182 // now has a focus. As a result, InputMethodManager will think t
hat mProxyView | 160 // now has a focus. As a result, InputMethodManager will think t
hat mProxyView |
183 // is focused, and will call getHandler() of the view when creat
ing input | 161 // is focused, and will call getHandler() of the view when creat
ing input |
184 // connection. | 162 // connection. |
185 | 163 |
186 // Step 1: Set mProxyView as InputMethodManager#mNextServedView. | 164 // Step 1: Set mProxyView as InputMethodManager#mNextServedView. |
187 // This does not affect the real window focus. | 165 // This does not affect the real window focus. |
188 mProxyView.onWindowFocusChanged(true); | 166 mProxyView.onWindowFocusChanged(true); |
189 | 167 |
190 // Step 2: Have InputMethodManager focus in on mNextServedView. | 168 // Step 2: Have InputMethodManager focus in on mNextServedView. |
191 // As a result, IMM will call onCreateInputConnection() on mProx
yView on the same | 169 // As a result, IMM will call onCreateInputConnection() on mProx
yView on the same |
192 // thread as mProxyView.getHandler(). It will also call subseque
nt InputConnection | 170 // thread as mProxyView.getHandler(). It will also call subseque
nt InputConnection |
193 // methods on this IME thread. | 171 // methods on this IME thread. |
194 mInputMethodManagerWrapper.isActive(view); | 172 mInputMethodManagerWrapper.isActive(view); |
195 | 173 |
196 // Step 3: Check that the above hack worked. | 174 // Step 3: Check that the above hack worked. |
197 // Do not check until activation finishes inside InputMethodMana
ger (on IME thread). | 175 // Do not check until activation finishes inside InputMethodMana
ger (on IME thread). |
198 if (mHandler == null) return; | |
199 mHandler.post(new Runnable() { | 176 mHandler.post(new Runnable() { |
200 @Override | 177 @Override |
201 public void run() { | 178 public void run() { |
202 postCheckRegisterResultOnUiThread(view, mCheckInvalidato
r, | 179 postCheckRegisterResultOnUiThread(view, mCheckInvalidato
r, |
203 CHECK_REGISTER_RETRY); | 180 CHECK_REGISTER_RETRY); |
204 } | 181 } |
205 }); | 182 }); |
206 } | 183 } |
207 }); | 184 }); |
208 } | 185 } |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
267 public void onViewFocusChanged(boolean gainFocus) { | 244 public void onViewFocusChanged(boolean gainFocus) { |
268 if (DEBUG_LOGS) Log.d(TAG, "onViewFocusChanged: " + gainFocus); | 245 if (DEBUG_LOGS) Log.d(TAG, "onViewFocusChanged: " + gainFocus); |
269 if (!gainFocus && mCheckInvalidator != null) mCheckInvalidator.invalidat
e(); | 246 if (!gainFocus && mCheckInvalidator != null) mCheckInvalidator.invalidat
e(); |
270 if (mProxyView != null) mProxyView.onOriginalViewFocusChanged(gainFocus)
; | 247 if (mProxyView != null) mProxyView.onOriginalViewFocusChanged(gainFocus)
; |
271 } | 248 } |
272 | 249 |
273 @Override | 250 @Override |
274 public void onViewAttachedToWindow() { | 251 public void onViewAttachedToWindow() { |
275 if (DEBUG_LOGS) Log.d(TAG, "onViewAttachedToWindow"); | 252 if (DEBUG_LOGS) Log.d(TAG, "onViewAttachedToWindow"); |
276 if (mProxyView != null) mProxyView.onOriginalViewAttachedToWindow(); | 253 if (mProxyView != null) mProxyView.onOriginalViewAttachedToWindow(); |
277 if (mHandler == null) mHandler = createHandler(); | |
278 } | 254 } |
279 | 255 |
280 @Override | 256 @Override |
281 public void onViewDetachedFromWindow() { | 257 public void onViewDetachedFromWindow() { |
282 if (DEBUG_LOGS) Log.d(TAG, "onViewDetachedFromWindow"); | 258 if (DEBUG_LOGS) Log.d(TAG, "onViewDetachedFromWindow"); |
283 if (mCheckInvalidator != null) mCheckInvalidator.invalidate(); | 259 if (mCheckInvalidator != null) mCheckInvalidator.invalidate(); |
284 if (mProxyView != null) mProxyView.onOriginalViewDetachedFromWindow(); | 260 if (mProxyView != null) mProxyView.onOriginalViewDetachedFromWindow(); |
285 destroyHandler(); | |
286 } | |
287 | |
288 @Override | |
289 public void destroy() { | |
290 if (mCheckInvalidator != null) mCheckInvalidator.invalidate(); | |
291 destroyHandler(); | |
292 } | 261 } |
293 } | 262 } |
OLD | NEW |