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.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN; 20 import static android.view.Display.DEFAULT_DISPLAY; 21 import static android.view.Display.INVALID_DISPLAY; 22 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; 23 import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE; 24 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; 25 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED; 26 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; 27 28 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; 29 import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE; 30 import static com.android.internal.inputmethod.SoftInputShowHideReason.REMOVE_IME_SCREENSHOT_FROM_IMMS; 31 import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_IME_SCREENSHOT_FROM_IMMS; 32 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState; 33 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult; 34 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT; 35 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_REMOVE_IME_SNAPSHOT; 36 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME_SNAPSHOT; 37 import static com.android.server.inputmethod.InputMethodManagerService.FALLBACK_DISPLAY_ID; 38 import static com.android.server.inputmethod.InputMethodManagerService.ImeDisplayValidator; 39 40 import static com.google.common.truth.Truth.assertThat; 41 42 import android.os.Binder; 43 import android.os.IBinder; 44 import android.os.RemoteException; 45 import android.view.inputmethod.InputMethodManager; 46 47 import androidx.test.ext.junit.runners.AndroidJUnit4; 48 49 import com.android.server.wm.ImeTargetChangeListener; 50 import com.android.server.wm.WindowManagerInternal; 51 52 import org.junit.Before; 53 import org.junit.Test; 54 import org.junit.runner.RunWith; 55 import org.mockito.ArgumentCaptor; 56 57 /** 58 * Test the behavior of {@link ImeVisibilityStateComputer} and {@link ImeVisibilityApplier} when 59 * requesting the IME visibility. 60 * 61 * Build/Install/Run: 62 * atest FrameworksInputMethodSystemServerTests:ImeVisibilityStateComputerTest 63 */ 64 @RunWith(AndroidJUnit4.class) 65 public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTestBase { 66 private ImeVisibilityStateComputer mComputer; 67 private ImeTargetChangeListener mListener; 68 private int mImeDisplayPolicy = DISPLAY_IME_POLICY_LOCAL; 69 70 @Before setUp()71 public void setUp() throws RemoteException { 72 super.setUp(); 73 ImeVisibilityStateComputer.Injector injector = new ImeVisibilityStateComputer.Injector() { 74 @Override 75 public WindowManagerInternal getWmService() { 76 return mMockWindowManagerInternal; 77 } 78 79 @Override 80 public ImeDisplayValidator getImeValidator() { 81 return displayId -> mImeDisplayPolicy; 82 } 83 }; 84 ArgumentCaptor<ImeTargetChangeListener> captor = ArgumentCaptor.forClass( 85 ImeTargetChangeListener.class); 86 verify(mMockWindowManagerInternal).setInputMethodTargetChangeListener(captor.capture()); 87 mComputer = new ImeVisibilityStateComputer(mInputMethodManagerService, injector); 88 mListener = captor.getValue(); 89 } 90 91 @Test testRequestImeVisibility_showImplicit()92 public void testRequestImeVisibility_showImplicit() { 93 initImeTargetWindowState(mWindowToken); 94 boolean res = mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT); 95 mComputer.requestImeVisibility(mWindowToken, res); 96 97 final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); 98 assertThat(state).isNotNull(); 99 assertThat(state.hasEditorFocused()).isTrue(); 100 assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED); 101 assertThat(state.isRequestedImeVisible()).isTrue(); 102 103 assertThat(mComputer.mRequestedShowExplicitly).isFalse(); 104 } 105 106 @Test testRequestImeVisibility_showExplicit()107 public void testRequestImeVisibility_showExplicit() { 108 initImeTargetWindowState(mWindowToken); 109 boolean res = mComputer.onImeShowFlags(null, 0 /* showFlags */); 110 mComputer.requestImeVisibility(mWindowToken, res); 111 112 final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); 113 assertThat(state).isNotNull(); 114 assertThat(state.hasEditorFocused()).isTrue(); 115 assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED); 116 assertThat(state.isRequestedImeVisible()).isTrue(); 117 118 assertThat(mComputer.mRequestedShowExplicitly).isTrue(); 119 } 120 121 /** 122 * This checks that the state after an explicit show request does not get reset during 123 * a subsequent implicit show request, without an intermediary hide request. 124 */ 125 @Test testRequestImeVisibility_showExplicit_thenShowImplicit()126 public void testRequestImeVisibility_showExplicit_thenShowImplicit() { 127 initImeTargetWindowState(mWindowToken); 128 mComputer.onImeShowFlags(null, 0 /* showFlags */); 129 assertThat(mComputer.mRequestedShowExplicitly).isTrue(); 130 131 mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT); 132 assertThat(mComputer.mRequestedShowExplicitly).isTrue(); 133 } 134 135 /** 136 * This checks that the state after a forced show request does not get reset during 137 * a subsequent explicit show request, without an intermediary hide request. 138 */ 139 @Test testRequestImeVisibility_showForced_thenShowExplicit()140 public void testRequestImeVisibility_showForced_thenShowExplicit() { 141 initImeTargetWindowState(mWindowToken); 142 mComputer.onImeShowFlags(null, InputMethodManager.SHOW_FORCED); 143 assertThat(mComputer.mShowForced).isTrue(); 144 145 mComputer.onImeShowFlags(null, 0 /* showFlags */); 146 assertThat(mComputer.mShowForced).isTrue(); 147 } 148 149 @Test testRequestImeVisibility_showImplicit_a11yNoImePolicy()150 public void testRequestImeVisibility_showImplicit_a11yNoImePolicy() { 151 // Precondition: set AccessibilityService#SHOW_MODE_HIDDEN policy 152 mComputer.getImePolicy().setA11yRequestNoSoftKeyboard(SHOW_MODE_HIDDEN); 153 154 initImeTargetWindowState(mWindowToken); 155 boolean res = mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT); 156 mComputer.requestImeVisibility(mWindowToken, res); 157 158 final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); 159 assertThat(state).isNotNull(); 160 assertThat(state.hasEditorFocused()).isTrue(); 161 assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED); 162 assertThat(state.isRequestedImeVisible()).isFalse(); 163 164 assertThat(mComputer.mRequestedShowExplicitly).isFalse(); 165 } 166 167 @Test testRequestImeVisibility_showImplicit_imeHiddenPolicy()168 public void testRequestImeVisibility_showImplicit_imeHiddenPolicy() { 169 // Precondition: set IME hidden display policy before calling showSoftInput 170 mComputer.getImePolicy().setImeHiddenByDisplayPolicy(true); 171 172 initImeTargetWindowState(mWindowToken); 173 boolean res = mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT); 174 mComputer.requestImeVisibility(mWindowToken, res); 175 176 final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); 177 assertThat(state).isNotNull(); 178 assertThat(state.hasEditorFocused()).isTrue(); 179 assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED); 180 assertThat(state.isRequestedImeVisible()).isFalse(); 181 182 assertThat(mComputer.mRequestedShowExplicitly).isFalse(); 183 } 184 185 @Test testRequestImeVisibility_hideNotAlways()186 public void testRequestImeVisibility_hideNotAlways() { 187 // Precondition: ensure IME has shown before hiding request. 188 mComputer.setInputShown(true); 189 190 initImeTargetWindowState(mWindowToken); 191 assertThat(mComputer.canHideIme(null, InputMethodManager.HIDE_NOT_ALWAYS)).isTrue(); 192 mComputer.requestImeVisibility(mWindowToken, false); 193 194 final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); 195 assertThat(state).isNotNull(); 196 assertThat(state.hasEditorFocused()).isTrue(); 197 assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED); 198 assertThat(state.isRequestedImeVisible()).isFalse(); 199 } 200 201 @Test testComputeImeDisplayId()202 public void testComputeImeDisplayId() { 203 final ImeTargetWindowState state = mComputer.getOrCreateWindowState(mWindowToken); 204 205 mImeDisplayPolicy = DISPLAY_IME_POLICY_LOCAL; 206 mComputer.computeImeDisplayId(state, DEFAULT_DISPLAY); 207 assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse(); 208 assertThat(state.getImeDisplayId()).isEqualTo(DEFAULT_DISPLAY); 209 210 mComputer.computeImeDisplayId(state, 10 /* displayId */); 211 assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse(); 212 assertThat(state.getImeDisplayId()).isEqualTo(10); 213 214 mImeDisplayPolicy = DISPLAY_IME_POLICY_HIDE; 215 mComputer.computeImeDisplayId(state, 10 /* displayId */); 216 assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isTrue(); 217 assertThat(state.getImeDisplayId()).isEqualTo(INVALID_DISPLAY); 218 219 mImeDisplayPolicy = DISPLAY_IME_POLICY_FALLBACK_DISPLAY; 220 mComputer.computeImeDisplayId(state, 10 /* displayId */); 221 assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse(); 222 assertThat(state.getImeDisplayId()).isEqualTo(FALLBACK_DISPLAY_ID); 223 } 224 225 @Test testComputeState_lastImeRequestedVisible_preserved_When_StateUnChanged()226 public void testComputeState_lastImeRequestedVisible_preserved_When_StateUnChanged() { 227 // Assume the last IME targeted window has requested IME visible 228 final IBinder lastImeTargetWindowToken = new Binder(); 229 mInputMethodManagerService.mLastImeTargetWindow = lastImeTargetWindowToken; 230 mComputer.requestImeVisibility(lastImeTargetWindowToken, true); 231 final ImeTargetWindowState lastState = mComputer.getWindowStateOrNull( 232 lastImeTargetWindowToken); 233 assertThat(lastState.isRequestedImeVisible()).isTrue(); 234 235 // Verify when focusing the next window with STATE_UNCHANGED flag, the last IME 236 // visibility state will be preserved to the current window state. 237 final ImeTargetWindowState stateWithUnChangedFlag = initImeTargetWindowState(mWindowToken); 238 mComputer.computeState(stateWithUnChangedFlag, true /* allowVisible */); 239 assertThat(stateWithUnChangedFlag.isRequestedImeVisible()).isEqualTo( 240 lastState.isRequestedImeVisible()); 241 } 242 243 @Test testOnInteractiveChanged()244 public void testOnInteractiveChanged() { 245 mComputer.getOrCreateWindowState(mWindowToken); 246 // Precondition: ensure IME has shown before hiding request. 247 mComputer.requestImeVisibility(mWindowToken, true); 248 mComputer.setInputShown(true); 249 250 // No need any visibility change When initially shows IME on the device was interactive. 251 ImeVisibilityStateComputer.ImeVisibilityResult result = mComputer.onInteractiveChanged( 252 mWindowToken, true /* interactive */); 253 assertThat(result).isNull(); 254 255 // Show the IME screenshot to capture the last IME visible state when the device inactive. 256 result = mComputer.onInteractiveChanged(mWindowToken, false /* interactive */); 257 assertThat(result).isNotNull(); 258 assertThat(result.getState()).isEqualTo(STATE_SHOW_IME_SNAPSHOT); 259 assertThat(result.getReason()).isEqualTo(SHOW_IME_SCREENSHOT_FROM_IMMS); 260 261 // Remove the IME screenshot when the device became interactive again. 262 result = mComputer.onInteractiveChanged(mWindowToken, true /* interactive */); 263 assertThat(result).isNotNull(); 264 assertThat(result.getState()).isEqualTo(STATE_REMOVE_IME_SNAPSHOT); 265 assertThat(result.getReason()).isEqualTo(REMOVE_IME_SCREENSHOT_FROM_IMMS); 266 } 267 268 @Test testOnApplyImeVisibilityFromComputer()269 public void testOnApplyImeVisibilityFromComputer() { 270 final IBinder testImeTargetOverlay = new Binder(); 271 final IBinder testImeInputTarget = new Binder(); 272 273 // Simulate a test IME input target was visible. 274 mListener.onImeInputTargetVisibilityChanged(testImeInputTarget, true, false); 275 276 // Simulate a test IME layering target overlay fully occluded the IME input target. 277 mListener.onImeTargetOverlayVisibilityChanged(testImeTargetOverlay, 278 TYPE_APPLICATION_OVERLAY, true, false); 279 mListener.onImeInputTargetVisibilityChanged(testImeInputTarget, false, false); 280 final ArgumentCaptor<IBinder> targetCaptor = ArgumentCaptor.forClass(IBinder.class); 281 final ArgumentCaptor<ImeVisibilityResult> resultCaptor = ArgumentCaptor.forClass( 282 ImeVisibilityResult.class); 283 verify(mInputMethodManagerService).onApplyImeVisibilityFromComputer(targetCaptor.capture(), 284 resultCaptor.capture()); 285 final IBinder imeInputTarget = targetCaptor.getValue(); 286 final ImeVisibilityResult result = resultCaptor.getValue(); 287 288 // Verify the computer will callback hiding IME state to IMMS. 289 assertThat(imeInputTarget).isEqualTo(testImeInputTarget); 290 assertThat(result.getState()).isEqualTo(STATE_HIDE_IME_EXPLICIT); 291 assertThat(result.getReason()).isEqualTo(HIDE_WHEN_INPUT_TARGET_INVISIBLE); 292 } 293 initImeTargetWindowState(IBinder windowToken)294 private ImeTargetWindowState initImeTargetWindowState(IBinder windowToken) { 295 final ImeTargetWindowState state = new ImeTargetWindowState(SOFT_INPUT_STATE_UNCHANGED, 296 0, true, true, true); 297 mComputer.setWindowState(windowToken, state); 298 return state; 299 } 300 } 301