blob: dbbe4b61c81c9219b25b91593c723ee81bcc4c88 [file] [log] [blame]
lumark970d9d22019-08-18 17:45:24 +08001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.view;
18
19import android.annotation.AnyThread;
20import android.annotation.NonNull;
21import android.annotation.UiThread;
22import android.util.Log;
23import android.view.inputmethod.InputMethodManager;
24
25import com.android.internal.inputmethod.InputMethodDebug;
26import com.android.internal.inputmethod.StartInputFlags;
27import com.android.internal.inputmethod.StartInputReason;
28
29/**
30 * Responsible for IME focus handling inside {@link ViewRootImpl}.
31 * @hide
32 */
33public final class ImeFocusController {
34 private static final boolean DEBUG = false;
35 private static final String TAG = "ImeFocusController";
36
37 private final ViewRootImpl mViewRootImpl;
38 private boolean mHasImeFocus = false;
39 private View mServedView;
40 private View mNextServedView;
lumark3f733cc2020-01-20 12:14:04 +080041 private InputMethodManagerDelegate mDelegate;
lumark970d9d22019-08-18 17:45:24 +080042
43 @UiThread
44 ImeFocusController(@NonNull ViewRootImpl viewRootImpl) {
45 mViewRootImpl = viewRootImpl;
46 }
47
Riddle Hsud4502792020-01-23 17:10:39 +080048 @NonNull
lumark970d9d22019-08-18 17:45:24 +080049 private InputMethodManagerDelegate getImmDelegate() {
Riddle Hsud4502792020-01-23 17:10:39 +080050 InputMethodManagerDelegate delegate = mDelegate;
51 if (delegate != null) {
52 return delegate;
lumark3f733cc2020-01-20 12:14:04 +080053 }
Riddle Hsud4502792020-01-23 17:10:39 +080054 delegate = mViewRootImpl.mContext.getSystemService(InputMethodManager.class).getDelegate();
55 mDelegate = delegate;
56 return delegate;
57 }
58
59 /** Called when the view root is moved to a different display. */
60 @UiThread
61 void onMovedToDisplay() {
62 // InputMethodManager managed its instances for different displays. So if the associated
63 // display is changed, the delegate also needs to be refreshed (by getImmDelegate).
64 // See the comment in {@link android.app.SystemServiceRegistry} for InputMethodManager
65 // and {@link android.view.inputmethod.InputMethodManager#forContext}.
66 mDelegate = null;
lumark970d9d22019-08-18 17:45:24 +080067 }
68
69 @UiThread
70 void onTraversal(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) {
71 final boolean hasImeFocus = updateImeFocusable(windowAttribute, false /* force */);
72 if (!hasWindowFocus || isInLocalFocusMode(windowAttribute)) {
73 return;
74 }
75 if (hasImeFocus == mHasImeFocus) {
76 return;
77 }
78 mHasImeFocus = hasImeFocus;
79 if (mHasImeFocus) {
80 onPreWindowFocus(true /* hasWindowFocus */, windowAttribute);
81 onPostWindowFocus(mViewRootImpl.mView.findFocus(), true /* hasWindowFocus */,
82 windowAttribute);
83 }
84 }
85
86 @UiThread
87 void onPreWindowFocus(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) {
88 if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
89 return;
90 }
91 if (hasWindowFocus) {
92 getImmDelegate().setCurrentRootView(mViewRootImpl);
93 }
94 }
95
96 @UiThread
97 boolean updateImeFocusable(WindowManager.LayoutParams windowAttribute, boolean force) {
98 final boolean hasImeFocus = WindowManager.LayoutParams.mayUseInputMethod(
99 windowAttribute.flags);
100 if (force) {
101 mHasImeFocus = hasImeFocus;
102 }
103 return hasImeFocus;
104 }
105
106 @UiThread
107 void onPostWindowFocus(View focusedView, boolean hasWindowFocus,
108 WindowManager.LayoutParams windowAttribute) {
109 if (!hasWindowFocus || !mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
110 return;
111 }
112 if (DEBUG) {
113 Log.v(TAG, "onWindowFocus: " + focusedView
114 + " softInputMode=" + InputMethodDebug.softInputModeToString(
115 windowAttribute.softInputMode));
116 }
117
118 boolean forceFocus = false;
Riddle Hsud4502792020-01-23 17:10:39 +0800119 final InputMethodManagerDelegate immDelegate = getImmDelegate();
120 if (immDelegate.isRestartOnNextWindowFocus(true /* reset */)) {
lumark970d9d22019-08-18 17:45:24 +0800121 if (DEBUG) Log.v(TAG, "Restarting due to isRestartOnNextWindowFocus as true");
122 forceFocus = true;
123 }
124 // Update mNextServedView when focusedView changed.
125 final View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView;
126 onViewFocusChanged(viewForWindowFocus, true);
127
Riddle Hsud4502792020-01-23 17:10:39 +0800128 immDelegate.startInputAsyncOnWindowFocusGain(viewForWindowFocus,
lumark970d9d22019-08-18 17:45:24 +0800129 windowAttribute.softInputMode, windowAttribute.flags, forceFocus);
130 }
131
132 public boolean checkFocus(boolean forceNewFocus, boolean startInput) {
Riddle Hsud4502792020-01-23 17:10:39 +0800133 final InputMethodManagerDelegate immDelegate = getImmDelegate();
134 if (!immDelegate.isCurrentRootView(mViewRootImpl)
lumark970d9d22019-08-18 17:45:24 +0800135 || (mServedView == mNextServedView && !forceNewFocus)) {
136 return false;
137 }
138 if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView
139 + " next=" + mNextServedView
140 + " force=" + forceNewFocus
141 + " package="
142 + (mServedView != null ? mServedView.getContext().getPackageName() : "<none>"));
143
144 // Close the connection when no next served view coming.
145 if (mNextServedView == null) {
Riddle Hsud4502792020-01-23 17:10:39 +0800146 immDelegate.finishInput();
147 immDelegate.closeCurrentIme();
lumark970d9d22019-08-18 17:45:24 +0800148 return false;
149 }
150 mServedView = mNextServedView;
Riddle Hsud4502792020-01-23 17:10:39 +0800151 immDelegate.finishComposingText();
lumark970d9d22019-08-18 17:45:24 +0800152
153 if (startInput) {
Riddle Hsud4502792020-01-23 17:10:39 +0800154 immDelegate.startInput(StartInputReason.CHECK_FOCUS, null /* focusedView */,
155 0 /* startInputFlags */, 0 /* softInputMode */, 0 /* windowFlags */);
lumark970d9d22019-08-18 17:45:24 +0800156 }
157 return true;
158 }
159
160 @UiThread
161 void onViewFocusChanged(View view, boolean hasFocus) {
162 if (view == null || view.isTemporarilyDetached()) {
163 return;
164 }
165 if (!getImmDelegate().isCurrentRootView(view.getViewRootImpl())) {
166 return;
167 }
lumarkec78f002020-03-08 00:43:23 +0800168 if (!view.hasImeFocus() || !view.hasWindowFocus()) {
lumark970d9d22019-08-18 17:45:24 +0800169 return;
170 }
lumarkec78f002020-03-08 00:43:23 +0800171 if (DEBUG) Log.d(TAG, "onViewFocusChanged, view=" + view + ", mServedView=" + mServedView);
172
Ming-Shin Luc7424a22020-04-01 01:57:06 +0800173 // We don't need to track the next served view when the view lost focus here because:
174 // 1) The current view focus may be cleared temporary when in touch mode, closing input
175 // at this moment isn't the right way.
176 // 2) We only care about the served view change when it focused, since changing input
177 // connection when the focus target changed is reasonable.
178 // 3) Setting the next served view as null when no more served view should be handled in
179 // other special events (e.g. view detached from window or the window dismissed).
lumarkec78f002020-03-08 00:43:23 +0800180 if (hasFocus) {
181 mNextServedView = view;
lumarkec78f002020-03-08 00:43:23 +0800182 }
lumark970d9d22019-08-18 17:45:24 +0800183 mViewRootImpl.dispatchCheckFocus();
184 }
185
186 @UiThread
187 void onViewDetachedFromWindow(View view) {
188 if (!getImmDelegate().isCurrentRootView(view.getViewRootImpl())) {
189 return;
190 }
191 if (mServedView == view) {
192 mNextServedView = null;
193 mViewRootImpl.dispatchCheckFocus();
194 }
195 }
196
197 @UiThread
198 void onWindowDismissed() {
Riddle Hsud4502792020-01-23 17:10:39 +0800199 final InputMethodManagerDelegate immDelegate = getImmDelegate();
200 if (!immDelegate.isCurrentRootView(mViewRootImpl)) {
lumark970d9d22019-08-18 17:45:24 +0800201 return;
202 }
203 if (mServedView != null) {
Riddle Hsud4502792020-01-23 17:10:39 +0800204 immDelegate.finishInput();
lumark970d9d22019-08-18 17:45:24 +0800205 }
Riddle Hsud4502792020-01-23 17:10:39 +0800206 immDelegate.setCurrentRootView(null);
lumark970d9d22019-08-18 17:45:24 +0800207 mHasImeFocus = false;
208 }
209
210 /**
lumark0df88122019-11-27 22:13:41 +0800211 * Called by {@link ViewRootImpl} to feedback the state of the screen for this view.
212 * @param newScreenState The new state of the screen. Can be either
213 * {@link View#SCREEN_STATE_ON} or {@link View#SCREEN_STATE_OFF}
214 */
215 @UiThread
216 void onScreenStateChanged(int newScreenState) {
217 if (!getImmDelegate().isCurrentRootView(mViewRootImpl)) {
218 return;
219 }
220 // Close input connection and IME when the screen is turn off for security concern.
221 if (newScreenState == View.SCREEN_STATE_OFF && mServedView != null) {
222 if (DEBUG) {
223 Log.d(TAG, "onScreenStateChanged, disconnect input when screen turned off");
224 }
225 mNextServedView = null;
226 mViewRootImpl.dispatchCheckFocus();
227 }
228 }
229
230 /**
lumark970d9d22019-08-18 17:45:24 +0800231 * @param windowAttribute {@link WindowManager.LayoutParams} to be checked.
232 * @return Whether the window is in local focus mode or not.
233 */
234 @AnyThread
235 private static boolean isInLocalFocusMode(WindowManager.LayoutParams windowAttribute) {
236 return (windowAttribute.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
237 }
238
239 int onProcessImeInputStage(Object token, InputEvent event,
240 WindowManager.LayoutParams windowAttribute,
241 InputMethodManager.FinishedInputEventCallback callback) {
242 if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
243 return InputMethodManager.DISPATCH_NOT_HANDLED;
244 }
245 final InputMethodManager imm =
246 mViewRootImpl.mContext.getSystemService(InputMethodManager.class);
247 if (imm == null) {
248 return InputMethodManager.DISPATCH_NOT_HANDLED;
249 }
250 return imm.dispatchInputEvent(event, token, callback, mViewRootImpl.mHandler);
251 }
252
253 /**
254 * A delegate implementing some basic {@link InputMethodManager} APIs.
255 * @hide
256 */
257 public interface InputMethodManagerDelegate {
258 boolean startInput(@StartInputReason int startInputReason, View focusedView,
259 @StartInputFlags int startInputFlags,
260 @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags);
261 void startInputAsyncOnWindowFocusGain(View rootView,
262 @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags,
263 boolean forceNewFocus);
264 void finishInput();
265 void closeCurrentIme();
266 void finishComposingText();
267 void setCurrentRootView(ViewRootImpl rootView);
268 boolean isCurrentRootView(ViewRootImpl rootView);
269 boolean isRestartOnNextWindowFocus(boolean reset);
270 }
271
272 public View getServedView() {
273 return mServedView;
274 }
275
276 public View getNextServedView() {
277 return mNextServedView;
278 }
279
280 public void setServedView(View view) {
281 mServedView = view;
282 }
283
284 public void setNextServedView(View view) {
285 mNextServedView = view;
286 }
lumark233164c2020-01-31 21:21:30 +0800287
288 /**
289 * Indicates whether the view's window has IME focused.
290 */
291 @UiThread
292 boolean hasImeFocus() {
293 return mHasImeFocus;
294 }
lumark970d9d22019-08-18 17:45:24 +0800295}