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