1 /* 2 * Copyright (C) 2022 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 com.android.server.inputmethod; 18 19 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; 20 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN; 21 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; 22 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED; 23 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; 24 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN; 25 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE; 26 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN; 27 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED; 28 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED; 29 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE; 30 31 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 32 33 import static com.google.common.truth.Truth.assertThat; 34 35 import static org.mockito.ArgumentMatchers.any; 36 import static org.mockito.ArgumentMatchers.anyInt; 37 import static org.mockito.Mockito.when; 38 39 import android.os.IBinder; 40 import android.os.LocaleList; 41 import android.os.RemoteException; 42 import android.util.Log; 43 import android.view.inputmethod.EditorInfo; 44 import android.window.ImeOnBackInvokedDispatcher; 45 46 import com.android.internal.inputmethod.IInputMethodClient; 47 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; 48 import com.android.internal.inputmethod.IRemoteInputConnection; 49 import com.android.internal.inputmethod.InputBindResult; 50 import com.android.internal.inputmethod.InputMethodDebug; 51 import com.android.internal.inputmethod.StartInputFlags; 52 import com.android.internal.inputmethod.StartInputReason; 53 import com.android.server.LocalServices; 54 import com.android.server.companion.virtual.VirtualDeviceManagerInternal; 55 import com.android.server.wm.WindowManagerInternal; 56 57 import org.junit.Test; 58 import org.junit.runner.RunWith; 59 import org.junit.runners.Parameterized; 60 import org.mockito.Mock; 61 62 import java.util.ArrayList; 63 import java.util.List; 64 65 /** 66 * Test the behavior of {@link InputMethodManagerService#startInputOrWindowGainedFocus(int, 67 * IInputMethodClient, IBinder, int, int, int, EditorInfo, IRemoteInputConnection, 68 * IRemoteAccessibilityInputConnection, int, int, ImeOnBackInvokedDispatcher)}. 69 */ 70 @RunWith(Parameterized.class) 71 public class InputMethodManagerServiceWindowGainedFocusTest 72 extends InputMethodManagerServiceTestBase { 73 private static final String TAG = "IMMSWindowGainedFocusTest"; 74 75 private static final int[] SOFT_INPUT_STATE_FLAGS = 76 new int[] { 77 SOFT_INPUT_STATE_UNSPECIFIED, 78 SOFT_INPUT_STATE_UNCHANGED, 79 SOFT_INPUT_STATE_HIDDEN, 80 SOFT_INPUT_STATE_ALWAYS_HIDDEN, 81 SOFT_INPUT_STATE_VISIBLE, 82 SOFT_INPUT_STATE_ALWAYS_VISIBLE 83 }; 84 private static final int[] SOFT_INPUT_ADJUST_FLAGS = 85 new int[] { 86 SOFT_INPUT_ADJUST_UNSPECIFIED, 87 SOFT_INPUT_ADJUST_RESIZE, 88 SOFT_INPUT_ADJUST_PAN, 89 SOFT_INPUT_ADJUST_NOTHING 90 }; 91 private static final int DEFAULT_SOFT_INPUT_FLAG = 92 StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR; 93 @Mock 94 VirtualDeviceManagerInternal mMockVdmInternal; 95 96 @Parameterized.Parameters(name = "softInputState={0}, softInputAdjustment={1}") softInputModeConfigs()97 public static List<Object[]> softInputModeConfigs() { 98 ArrayList<Object[]> params = new ArrayList<>(); 99 for (int softInputState : SOFT_INPUT_STATE_FLAGS) { 100 for (int softInputAdjust : SOFT_INPUT_ADJUST_FLAGS) { 101 params.add(new Object[] {softInputState, softInputAdjust}); 102 } 103 } 104 return params; 105 } 106 107 private final int mSoftInputState; 108 private final int mSoftInputAdjustment; 109 InputMethodManagerServiceWindowGainedFocusTest( int softInputState, int softInputAdjustment)110 public InputMethodManagerServiceWindowGainedFocusTest( 111 int softInputState, int softInputAdjustment) { 112 mSoftInputState = softInputState; 113 mSoftInputAdjustment = softInputAdjustment; 114 } 115 116 @Test startInputOrWindowGainedFocus_forwardNavigation()117 public void startInputOrWindowGainedFocus_forwardNavigation() throws RemoteException { 118 mockHasImeFocusAndRestoreImeVisibility(false /* restoreImeVisibility */); 119 120 assertThat( 121 startInputOrWindowGainedFocus( 122 DEFAULT_SOFT_INPUT_FLAG, true /* forwardNavigation */)) 123 .isEqualTo(SUCCESS_WAITING_IME_BINDING_RESULT); 124 125 switch (mSoftInputState) { 126 case SOFT_INPUT_STATE_UNSPECIFIED: 127 boolean showSoftInput = 128 (mSoftInputAdjustment == SOFT_INPUT_ADJUST_RESIZE) || mIsLargeScreen; 129 verifyShowSoftInput( 130 showSoftInput /* setVisible */, showSoftInput /* showSoftInput */); 131 // Soft input was hidden by default, so it doesn't need to call 132 // {@code IMS#hideSoftInput()}. 133 verifyHideSoftInput(!showSoftInput /* setNotVisible */, false /* hideSoftInput */); 134 break; 135 case SOFT_INPUT_STATE_VISIBLE: 136 case SOFT_INPUT_STATE_ALWAYS_VISIBLE: 137 verifyShowSoftInput(true /* setVisible */, true /* showSoftInput */); 138 verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */); 139 break; 140 case SOFT_INPUT_STATE_UNCHANGED: // Do nothing 141 verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */); 142 verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */); 143 break; 144 case SOFT_INPUT_STATE_HIDDEN: 145 case SOFT_INPUT_STATE_ALWAYS_HIDDEN: 146 verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */); 147 // Soft input was hidden by default, so it doesn't need to call 148 // {@code IMS#hideSoftInput()}. 149 verifyHideSoftInput(true /* setNotVisible */, false /* hideSoftInput */); 150 break; 151 default: 152 throw new IllegalStateException( 153 "Unhandled soft input mode: " 154 + InputMethodDebug.softInputModeToString(mSoftInputState)); 155 } 156 } 157 158 @Test startInputOrWindowGainedFocus_notForwardNavigation()159 public void startInputOrWindowGainedFocus_notForwardNavigation() throws RemoteException { 160 mockHasImeFocusAndRestoreImeVisibility(false /* restoreImeVisibility */); 161 162 assertThat( 163 startInputOrWindowGainedFocus( 164 DEFAULT_SOFT_INPUT_FLAG, false /* forwardNavigation */)) 165 .isEqualTo(SUCCESS_WAITING_IME_BINDING_RESULT); 166 167 switch (mSoftInputState) { 168 case SOFT_INPUT_STATE_UNSPECIFIED: 169 boolean hideSoftInput = 170 (mSoftInputAdjustment != SOFT_INPUT_ADJUST_RESIZE) && !mIsLargeScreen; 171 verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */); 172 // Soft input was hidden by default, so it doesn't need to call 173 // {@code IMS#hideSoftInput()}. 174 verifyHideSoftInput(hideSoftInput /* setNotVisible */, false /* hideSoftInput */); 175 break; 176 case SOFT_INPUT_STATE_VISIBLE: 177 case SOFT_INPUT_STATE_HIDDEN: 178 case SOFT_INPUT_STATE_UNCHANGED: // Do nothing 179 verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */); 180 verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */); 181 break; 182 case SOFT_INPUT_STATE_ALWAYS_VISIBLE: 183 verifyShowSoftInput(true /* setVisible */, true /* showSoftInput */); 184 verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */); 185 break; 186 case SOFT_INPUT_STATE_ALWAYS_HIDDEN: 187 verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */); 188 // Soft input was hidden by default, so it doesn't need to call 189 // {@code IMS#hideSoftInput()}. 190 verifyHideSoftInput(true /* setNotVisible */, false /* hideSoftInput */); 191 break; 192 default: 193 throw new IllegalStateException( 194 "Unhandled soft input mode: " 195 + InputMethodDebug.softInputModeToString(mSoftInputState)); 196 } 197 } 198 199 @Test startInputOrWindowGainedFocus_userNotRunning()200 public void startInputOrWindowGainedFocus_userNotRunning() throws RemoteException { 201 when(mMockUserManagerInternal.isUserRunning(anyInt())).thenReturn(false); 202 203 assertThat( 204 startInputOrWindowGainedFocus( 205 DEFAULT_SOFT_INPUT_FLAG, true /* forwardNavigation */)) 206 .isEqualTo(InputBindResult.INVALID_USER); 207 verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */); 208 verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */); 209 } 210 211 @Test startInputOrWindowGainedFocus_invalidFocusStatus()212 public void startInputOrWindowGainedFocus_invalidFocusStatus() throws RemoteException { 213 int[] invalidImeClientFocus = 214 new int[] { 215 WindowManagerInternal.ImeClientFocusResult.NOT_IME_TARGET_WINDOW, 216 WindowManagerInternal.ImeClientFocusResult.DISPLAY_ID_MISMATCH, 217 WindowManagerInternal.ImeClientFocusResult.INVALID_DISPLAY_ID 218 }; 219 InputBindResult[] inputBingResult = 220 new InputBindResult[] { 221 InputBindResult.NOT_IME_TARGET_WINDOW, 222 InputBindResult.DISPLAY_ID_MISMATCH, 223 InputBindResult.INVALID_DISPLAY_ID 224 }; 225 226 for (int i = 0; i < invalidImeClientFocus.length; i++) { 227 when(mMockWindowManagerInternal.hasInputMethodClientFocus( 228 any(), anyInt(), anyInt(), anyInt())) 229 .thenReturn(invalidImeClientFocus[i]); 230 231 assertThat( 232 startInputOrWindowGainedFocus( 233 DEFAULT_SOFT_INPUT_FLAG, true /* forwardNavigation */)) 234 .isEqualTo(inputBingResult[i]); 235 verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */); 236 verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */); 237 } 238 } 239 startInputOrWindowGainedFocus( int startInputFlag, boolean forwardNavigation)240 private InputBindResult startInputOrWindowGainedFocus( 241 int startInputFlag, boolean forwardNavigation) { 242 int softInputMode = mSoftInputState | mSoftInputAdjustment; 243 if (forwardNavigation) { 244 softInputMode |= SOFT_INPUT_IS_FORWARD_NAVIGATION; 245 } 246 247 Log.i( 248 TAG, 249 "startInputOrWindowGainedFocus() softInputStateFlag=" 250 + InputMethodDebug.softInputModeToString(mSoftInputState) 251 + ", softInputAdjustFlag=" 252 + InputMethodDebug.softInputModeToString(mSoftInputAdjustment)); 253 254 return mInputMethodManagerService.startInputOrWindowGainedFocus( 255 StartInputReason.WINDOW_FOCUS_GAIN /* startInputReason */, 256 mMockInputMethodClient /* client */, 257 mWindowToken /* windowToken */, 258 startInputFlag /* startInputFlags */, 259 softInputMode /* softInputMode */, 260 0 /* windowFlags */, 261 mEditorInfo /* editorInfo */, 262 mMockRemoteInputConnection /* inputConnection */, 263 mMockRemoteAccessibilityInputConnection /* remoteAccessibilityInputConnection */, 264 mTargetSdkVersion /* unverifiedTargetSdkVersion */, 265 mCallingUserId /* userId */, 266 mMockImeOnBackInvokedDispatcher /* imeDispatcher */); 267 } 268 269 @Test startInputOrWindowGainedFocus_localeHintsOverride()270 public void startInputOrWindowGainedFocus_localeHintsOverride() throws RemoteException { 271 doReturn(mMockVdmInternal).when( 272 () -> LocalServices.getService(VirtualDeviceManagerInternal.class)); 273 LocaleList overrideLocale = LocaleList.forLanguageTags("zh-CN"); 274 doReturn(overrideLocale).when(mMockVdmInternal).getPreferredLocaleListForUid(anyInt()); 275 mockHasImeFocusAndRestoreImeVisibility(false /* restoreImeVisibility */); 276 277 assertThat(startInputOrWindowGainedFocus(DEFAULT_SOFT_INPUT_FLAG, 278 true /* forwardNavigation */)).isEqualTo(SUCCESS_WAITING_IME_BINDING_RESULT); 279 assertThat(mEditorInfo.hintLocales).isEqualTo(overrideLocale); 280 } 281 mockHasImeFocusAndRestoreImeVisibility(boolean restoreImeVisibility)282 private void mockHasImeFocusAndRestoreImeVisibility(boolean restoreImeVisibility) { 283 when(mMockWindowManagerInternal.hasInputMethodClientFocus( 284 any(), anyInt(), anyInt(), anyInt())) 285 .thenReturn(WindowManagerInternal.ImeClientFocusResult.HAS_IME_FOCUS); 286 when(mMockWindowManagerInternal.shouldRestoreImeVisibility(any())) 287 .thenReturn(restoreImeVisibility); 288 } 289 } 290