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