blob: 6784cf7407fadece1f4a996458f354b3762bec37 [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
173 if (hasFocus) {
174 mNextServedView = view;
175 } else if (view == mServedView) {
176 mNextServedView = null;
177 }
lumark970d9d22019-08-18 17:45:24 +0800178 mViewRootImpl.dispatchCheckFocus();
179 }
180
181 @UiThread
182 void onViewDetachedFromWindow(View view) {
183 if (!getImmDelegate().isCurrentRootView(view.getViewRootImpl())) {
184 return;
185 }
186 if (mServedView == view) {
187 mNextServedView = null;
188 mViewRootImpl.dispatchCheckFocus();
189 }
190 }
191
192 @UiThread
193 void onWindowDismissed() {
Riddle Hsud4502792020-01-23 17:10:39 +0800194 final InputMethodManagerDelegate immDelegate = getImmDelegate();
195 if (!immDelegate.isCurrentRootView(mViewRootImpl)) {
lumark970d9d22019-08-18 17:45:24 +0800196 return;
197 }
198 if (mServedView != null) {
Riddle Hsud4502792020-01-23 17:10:39 +0800199 immDelegate.finishInput();
lumark970d9d22019-08-18 17:45:24 +0800200 }
Riddle Hsud4502792020-01-23 17:10:39 +0800201 immDelegate.setCurrentRootView(null);
lumark970d9d22019-08-18 17:45:24 +0800202 mHasImeFocus = false;
203 }
204
205 /**
lumark0df88122019-11-27 22:13:41 +0800206 * Called by {@link ViewRootImpl} to feedback the state of the screen for this view.
207 * @param newScreenState The new state of the screen. Can be either
208 * {@link View#SCREEN_STATE_ON} or {@link View#SCREEN_STATE_OFF}
209 */
210 @UiThread
211 void onScreenStateChanged(int newScreenState) {
212 if (!getImmDelegate().isCurrentRootView(mViewRootImpl)) {
213 return;
214 }
215 // Close input connection and IME when the screen is turn off for security concern.
216 if (newScreenState == View.SCREEN_STATE_OFF && mServedView != null) {
217 if (DEBUG) {
218 Log.d(TAG, "onScreenStateChanged, disconnect input when screen turned off");
219 }
220 mNextServedView = null;
221 mViewRootImpl.dispatchCheckFocus();
222 }
223 }
224
225 /**
lumark970d9d22019-08-18 17:45:24 +0800226 * @param windowAttribute {@link WindowManager.LayoutParams} to be checked.
227 * @return Whether the window is in local focus mode or not.
228 */
229 @AnyThread
230 private static boolean isInLocalFocusMode(WindowManager.LayoutParams windowAttribute) {
231 return (windowAttribute.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
232 }
233
234 int onProcessImeInputStage(Object token, InputEvent event,
235 WindowManager.LayoutParams windowAttribute,
236 InputMethodManager.FinishedInputEventCallback callback) {
237 if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
238 return InputMethodManager.DISPATCH_NOT_HANDLED;
239 }
240 final InputMethodManager imm =
241 mViewRootImpl.mContext.getSystemService(InputMethodManager.class);
242 if (imm == null) {
243 return InputMethodManager.DISPATCH_NOT_HANDLED;
244 }
245 return imm.dispatchInputEvent(event, token, callback, mViewRootImpl.mHandler);
246 }
247
248 /**
249 * A delegate implementing some basic {@link InputMethodManager} APIs.
250 * @hide
251 */
252 public interface InputMethodManagerDelegate {
253 boolean startInput(@StartInputReason int startInputReason, View focusedView,
254 @StartInputFlags int startInputFlags,
255 @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags);
256 void startInputAsyncOnWindowFocusGain(View rootView,
257 @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags,
258 boolean forceNewFocus);
259 void finishInput();
260 void closeCurrentIme();
261 void finishComposingText();
262 void setCurrentRootView(ViewRootImpl rootView);
263 boolean isCurrentRootView(ViewRootImpl rootView);
264 boolean isRestartOnNextWindowFocus(boolean reset);
265 }
266
267 public View getServedView() {
268 return mServedView;
269 }
270
271 public View getNextServedView() {
272 return mNextServedView;
273 }
274
275 public void setServedView(View view) {
276 mServedView = view;
277 }
278
279 public void setNextServedView(View view) {
280 mNextServedView = view;
281 }
lumark233164c2020-01-31 21:21:30 +0800282
283 /**
284 * Indicates whether the view's window has IME focused.
285 */
286 @UiThread
287 boolean hasImeFocus() {
288 return mHasImeFocus;
289 }
lumark970d9d22019-08-18 17:45:24 +0800290}