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

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

Issue 2537073002: Fix leaks in InputConnectionHandlerThread (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
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.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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698