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