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; | |
Torne
2016/11/29 13:22:55
Why is this posted to the handler just to post bac
Changwan Ryu
2016/11/29 23:33:18
In most cases, InputMethodManager should access ou
Torne
2016/11/30 10:33:35
I don't understand what you mean here at all; mayb
| |
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, selectionStart, selectionEnd, outAttrs); | 130 inputType, inputFlags, 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(); | |
Torne
2016/11/29 13:22:54
Destroying it and recreating it on detach/reattach
Changwan Ryu
2016/11/29 23:33:18
If we're considering multiple WebView cases more c
Torne
2016/11/30 10:33:35
I don't understand your answer here either; what w
| |
286 } | |
287 | |
288 @Override | |
289 public void destroy() { | |
Torne
2016/11/29 13:22:55
Normally we expect that destroy() methods leave ob
Ted C
2016/11/29 22:32:16
Maybe reset would be a better name for this then.
Changwan Ryu
2016/11/29 23:33:18
Hmm... We already have resetAndHidKeyboard() which
Torne
2016/11/30 10:33:35
Well, if we have a single handler thread then ther
| |
290 if (mCheckInvalidator != null) mCheckInvalidator.invalidate(); | |
291 destroyHandler(); | |
261 } | 292 } |
262 } | 293 } |
OLD | NEW |