Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(433)

Side by Side Diff: content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionFactory.java

Issue 2544163003: Simplify handler initialization for InputConnectionFactory (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | content/public/android/junit/src/org/chromium/content/browser/input/ThreadedInputConnectionFactoryTest.java » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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 }
OLDNEW
« no previous file with comments | « no previous file | content/public/android/junit/src/org/chromium/content/browser/input/ThreadedInputConnectionFactoryTest.java » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698