• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package android.view;
18 
19 import static android.view.ImeFocusControllerProto.HAS_IME_FOCUS;
20 
21 import android.annotation.AnyThread;
22 import android.annotation.NonNull;
23 import android.annotation.UiThread;
24 import android.util.Log;
25 import android.util.proto.ProtoOutputStream;
26 import android.view.inputmethod.Flags;
27 import android.view.inputmethod.InputMethodManager;
28 
29 import com.android.internal.inputmethod.InputMethodDebug;
30 
31 /**
32  * Responsible for IME focus handling inside {@link ViewRootImpl}.
33  * @hide
34  */
35 public final class ImeFocusController {
36     private static final boolean DEBUG = false;
37     private static final String TAG = "ImeFocusController";
38 
39     private final ViewRootImpl mViewRootImpl;
40     private boolean mHasImeFocus = false;
41     private InputMethodManagerDelegate mDelegate;
42 
43     @UiThread
ImeFocusController(@onNull ViewRootImpl viewRootImpl)44     ImeFocusController(@NonNull ViewRootImpl viewRootImpl) {
45         mViewRootImpl = viewRootImpl;
46     }
47 
48     @NonNull
getImmDelegate()49     private InputMethodManagerDelegate getImmDelegate() {
50         if (mDelegate == null) {
51             mDelegate = mViewRootImpl.mContext.getSystemService(
52                     InputMethodManager.class).getDelegate();
53         }
54         return mDelegate;
55     }
56 
57     /** Called when the view root is moved to a different display. */
58     @UiThread
onMovedToDisplay()59     void onMovedToDisplay() {
60         // InputMethodManager managed its instances for different displays. So if the associated
61         // display is changed, the delegate also needs to be refreshed (by getImmDelegate).
62         // See the comment in {@link android.app.SystemServiceRegistry} for InputMethodManager
63         // and {@link android.view.inputmethod.InputMethodManager#forContext}.
64         mDelegate = null;
65     }
66 
67     @UiThread
onTraversal(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute)68     void onTraversal(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) {
69         final boolean hasImeFocus = WindowManager.LayoutParams.mayUseInputMethod(
70                 windowAttribute.flags);
71         if (!hasWindowFocus || isInLocalFocusMode(windowAttribute)) {
72             return;
73         }
74         if (hasImeFocus == mHasImeFocus) {
75             return;
76         }
77         mHasImeFocus = hasImeFocus;
78         if (mHasImeFocus) {
79             getImmDelegate().onPreWindowGainedFocus(mViewRootImpl);
80             final View focusedView = mViewRootImpl.mView.findFocus();
81             View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView;
82             getImmDelegate().onPostWindowGainedFocus(viewForWindowFocus, windowAttribute);
83         }
84     }
85 
86     @UiThread
onPreWindowFocus(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute)87     void onPreWindowFocus(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) {
88         mHasImeFocus = WindowManager.LayoutParams.mayUseInputMethod(windowAttribute.flags);
89         if (!hasWindowFocus || !mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
90             if (!hasWindowFocus) {
91                 getImmDelegate().onWindowLostFocus(mViewRootImpl);
92             }
93         } else {
94             getImmDelegate().onPreWindowGainedFocus(mViewRootImpl);
95         }
96     }
97 
98     @UiThread
onPostWindowFocus(View focusedView, boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute)99     void onPostWindowFocus(View focusedView, boolean hasWindowFocus,
100             WindowManager.LayoutParams windowAttribute) {
101         if (!hasWindowFocus || !mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
102             return;
103         }
104         View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView;
105         if (DEBUG) {
106             Log.v(TAG, "onWindowFocus: " + viewForWindowFocus
107                     + " softInputMode=" + InputMethodDebug.softInputModeToString(
108                     windowAttribute.softInputMode));
109         }
110 
111         getImmDelegate().onPostWindowGainedFocus(viewForWindowFocus, windowAttribute);
112     }
113 
114     /**
115      * @see ViewRootImpl#dispatchCheckFocus()
116      */
117     @UiThread
onScheduledCheckFocus()118     void onScheduledCheckFocus() {
119         getImmDelegate().onScheduledCheckFocus(mViewRootImpl);
120     }
121 
122     @UiThread
onViewFocusChanged(View view, boolean hasFocus)123     void onViewFocusChanged(View view, boolean hasFocus) {
124         getImmDelegate().onViewFocusChanged(view, hasFocus);
125     }
126 
127     @UiThread
onViewDetachedFromWindow(View view)128     void onViewDetachedFromWindow(View view) {
129         getImmDelegate().onViewDetachedFromWindow(view, mViewRootImpl);
130 
131     }
132 
133     @UiThread
onWindowDismissed()134     void onWindowDismissed() {
135         getImmDelegate().onWindowDismissed(mViewRootImpl);
136         mHasImeFocus = false;
137     }
138 
139     /**
140      * @param windowAttribute {@link WindowManager.LayoutParams} to be checked.
141      * @return Whether the window is in local focus mode or not.
142      */
143     @AnyThread
isInLocalFocusMode(WindowManager.LayoutParams windowAttribute)144     private static boolean isInLocalFocusMode(WindowManager.LayoutParams windowAttribute) {
145         return (windowAttribute.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
146     }
147 
onProcessImeInputStage(Object token, InputEvent event, WindowManager.LayoutParams windowAttribute, InputMethodManager.FinishedInputEventCallback callback)148     int onProcessImeInputStage(Object token, InputEvent event,
149             WindowManager.LayoutParams windowAttribute,
150             InputMethodManager.FinishedInputEventCallback callback) {
151         if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
152             return InputMethodManager.DISPATCH_NOT_HANDLED;
153         }
154         if (Flags.refactorInsetsController() && event instanceof KeyEvent keyEvent
155                 && keyEvent.getKeyCode() == KeyEvent.KEYCODE_BACK) {
156             final var insetsController = mViewRootImpl.getInsetsController();
157             if (insetsController.getAnimationType(WindowInsets.Type.ime())
158                     == InsetsController.ANIMATION_TYPE_HIDE
159                     || insetsController.isPredictiveBackImeHideAnimInProgress()) {
160                 // if there is an ongoing hide animation, the back event should not be dispatched
161                 // to the IME.
162                 return InputMethodManager.DISPATCH_NOT_HANDLED;
163             }
164         }
165         final InputMethodManager imm =
166                 mViewRootImpl.mContext.getSystemService(InputMethodManager.class);
167         if (imm == null) {
168             return InputMethodManager.DISPATCH_NOT_HANDLED;
169         }
170         return imm.dispatchInputEvent(event, token, callback, mViewRootImpl.mHandler);
171     }
172 
173     /**
174      * A delegate implementing some basic {@link InputMethodManager} APIs.
175      * @hide
176      */
177     public interface InputMethodManagerDelegate {
onPreWindowGainedFocus(ViewRootImpl viewRootImpl)178         void onPreWindowGainedFocus(ViewRootImpl viewRootImpl);
onPostWindowGainedFocus(View viewForWindowFocus, @NonNull WindowManager.LayoutParams windowAttribute)179         void onPostWindowGainedFocus(View viewForWindowFocus,
180                 @NonNull WindowManager.LayoutParams windowAttribute);
onWindowLostFocus(@onNull ViewRootImpl viewRootImpl)181         void onWindowLostFocus(@NonNull ViewRootImpl viewRootImpl);
onViewFocusChanged(@onNull View view, boolean hasFocus)182         void onViewFocusChanged(@NonNull View view, boolean hasFocus);
onScheduledCheckFocus(@onNull ViewRootImpl viewRootImpl)183         void onScheduledCheckFocus(@NonNull ViewRootImpl viewRootImpl);
onViewDetachedFromWindow(View view, ViewRootImpl viewRootImpl)184         void onViewDetachedFromWindow(View view, ViewRootImpl viewRootImpl);
onWindowDismissed(ViewRootImpl viewRootImpl)185         void onWindowDismissed(ViewRootImpl viewRootImpl);
186     }
187 
188     /**
189      * Indicates whether the view's window has IME focused.
190      */
191     @UiThread
hasImeFocus()192     boolean hasImeFocus() {
193         return mHasImeFocus;
194     }
195 
dumpDebug(ProtoOutputStream proto, long fieldId)196     void dumpDebug(ProtoOutputStream proto, long fieldId) {
197         final long token = proto.start(fieldId);
198         proto.write(HAS_IME_FOCUS, mHasImeFocus);
199         proto.end(token);
200     }
201 }
202