1 /* 2 * Copyright (C) 2021 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.inputmethod.stresstest; 18 19 import static com.android.inputmethod.stresstest.ImeStressTestUtil.REQUEST_FOCUS_ON_CREATE; 20 import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity; 21 import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity.createIntent; 22 import static com.android.inputmethod.stresstest.ImeStressTestUtil.UNFOCUSABLE_VIEW; 23 import static com.android.inputmethod.stresstest.ImeStressTestUtil.callOnMainSync; 24 import static com.android.inputmethod.stresstest.ImeStressTestUtil.getWindowAndSoftInputFlagParameters; 25 import static com.android.inputmethod.stresstest.ImeStressTestUtil.hasUnfocusableWindowFlags; 26 import static com.android.inputmethod.stresstest.ImeStressTestUtil.requestFocusAndVerify; 27 import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeAlwaysHiddenWithWindowFlagSet; 28 import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeIsAlwaysHidden; 29 import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyWindowAndViewFocus; 30 import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown; 31 32 import static com.google.common.truth.Truth.assertThat; 33 34 import android.app.Instrumentation; 35 import android.content.Intent; 36 import android.content.res.Configuration; 37 import android.os.SystemClock; 38 import android.platform.test.annotations.RootPermissionTest; 39 import android.platform.test.rule.UnlockScreenRule; 40 import android.support.test.uiautomator.By; 41 import android.support.test.uiautomator.UiDevice; 42 import android.support.test.uiautomator.UiObject2; 43 import android.support.test.uiautomator.Until; 44 import android.view.WindowManager; 45 import android.widget.EditText; 46 47 import androidx.test.platform.app.InstrumentationRegistry; 48 49 import org.junit.Rule; 50 import org.junit.Test; 51 import org.junit.runner.RunWith; 52 import org.junit.runners.Parameterized; 53 54 import java.util.Collections; 55 import java.util.List; 56 57 /** 58 * Tests to verify the "auto-show" behavior in {@code InputMethodManagerService} when the window 59 * gaining the focus to start the input. 60 */ 61 @RootPermissionTest 62 @RunWith(Parameterized.class) 63 public final class AutoShowTest { 64 65 @Rule(order = 0) public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule(); 66 @Rule(order = 1) public ImeStressTestRule mImeStressTestRule = 67 new ImeStressTestRule(true /* useSimpleTestIme */); 68 @Rule(order = 2) public ScreenCaptureRule mScreenCaptureRule = 69 new ScreenCaptureRule("/sdcard/InputMethodStressTest"); 70 @Parameterized.Parameters( 71 name = "windowFocusFlags={0}, softInputVisibility={1}, softInputAdjustment={2}") windowAndSoftInputFlagParameters()72 public static List<Object[]> windowAndSoftInputFlagParameters() { 73 return getWindowAndSoftInputFlagParameters(); 74 } 75 76 private final int mSoftInputFlags; 77 private final int mWindowFocusFlags; 78 private final Instrumentation mInstrumentation; 79 private final boolean mIsLargeScreen; 80 AutoShowTest(int windowFocusFlags, int softInputVisibility, int softInputAdjustment)81 public AutoShowTest(int windowFocusFlags, int softInputVisibility, int softInputAdjustment) { 82 mSoftInputFlags = softInputVisibility | softInputAdjustment; 83 mWindowFocusFlags = windowFocusFlags; 84 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 85 mIsLargeScreen = mInstrumentation.getContext().getResources() 86 .getConfiguration().isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE); 87 } 88 89 /** 90 * Test auto-show IME behavior when the {@link EditText} is focusable ({@link 91 * EditText#isFocusableInTouchMode} is {@code true}) and has called {@link 92 * EditText#requestFocus}. 93 */ 94 @Test autoShow_hasFocusedView_requestFocus()95 public void autoShow_hasFocusedView_requestFocus() { 96 // request focus at onCreate() 97 Intent intent = 98 createIntent( 99 mWindowFocusFlags, 100 mSoftInputFlags, 101 Collections.singletonList(REQUEST_FOCUS_ON_CREATE)); 102 TestActivity activity = TestActivity.start(intent); 103 104 verifyAutoShowBehavior_forwardWithKeyboardOff(activity); 105 } 106 107 /** 108 * Test auto-show IME behavior when the {@link EditText} is focusable ({@link 109 * EditText#isFocusableInTouchMode} is {@code true}) and {@link EditText#requestFocus} is not 110 * called. The IME should never be shown because there is no focused editor in the window. 111 */ 112 @Test autoShow_hasFocusedView_notRequestFocus()113 public void autoShow_hasFocusedView_notRequestFocus() { 114 // request focus not set 115 Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); 116 TestActivity activity = TestActivity.start(intent); 117 EditText editText = activity.getEditText(); 118 119 int windowFlags = activity.getWindow().getAttributes().flags; 120 if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) { 121 // When FLAG_NOT_FOCUSABLE is set true, the view will never gain window focus. 122 verifyWindowAndViewFocus( 123 editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ false); 124 } else { 125 verifyWindowAndViewFocus( 126 editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false); 127 } 128 // IME is always hidden because there is no view focus. 129 verifyImeIsAlwaysHidden(editText); 130 } 131 132 /** 133 * Test auto-show IME behavior when the {@link EditText} is not focusable ({@link 134 * EditText#isFocusableInTouchMode} is {@code false}) and {@link EditText#requestFocus} is not 135 * called. The IME should never be shown because there is no focusable editor in the window. 136 */ 137 @Test autoShow_notFocusedView_notRequestFocus()138 public void autoShow_notFocusedView_notRequestFocus() { 139 // Unfocusable view, request focus not set 140 Intent intent = 141 createIntent( 142 mWindowFocusFlags, 143 mSoftInputFlags, 144 Collections.singletonList(UNFOCUSABLE_VIEW)); 145 TestActivity activity = TestActivity.start(intent); 146 EditText editText = activity.getEditText(); 147 148 int windowFlags = activity.getWindow().getAttributes().flags; 149 if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) { 150 // When FLAG_NOT_FOCUSABLE is set true, the view will never gain window focus. 151 verifyWindowAndViewFocus( 152 editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ false); 153 } else { 154 verifyWindowAndViewFocus( 155 editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false); 156 } 157 // IME is always hidden because there is no focused view. 158 verifyImeIsAlwaysHidden(editText); 159 } 160 161 /** 162 * Test auto-show IME behavior when the activity is navigated forward from another activity with 163 * keyboard off. 164 */ 165 @Test autoShow_forwardWithKeyboardOff()166 public void autoShow_forwardWithKeyboardOff() { 167 // Create first activity with keyboard off 168 Intent intent1 = 169 createIntent( 170 0x0 /* No window focus flags */, 171 WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED 172 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE, 173 Collections.emptyList()); 174 TestActivity firstActivity = TestActivity.start(intent1); 175 176 // Create second activity with parameterized flags: 177 Intent intent2 = 178 createIntent( 179 mWindowFocusFlags, 180 mSoftInputFlags, 181 Collections.singletonList(REQUEST_FOCUS_ON_CREATE)); 182 TestActivity secondActivity = firstActivity.startSecondTestActivity(intent2); 183 184 // The auto-show behavior should be the same as opening the app 185 verifyAutoShowBehavior_forwardWithKeyboardOff(secondActivity); 186 } 187 188 /** 189 * Test auto-show IME behavior when the activity is navigated forward from another activity with 190 * keyboard on. 191 */ 192 @Test autoShow_forwardWithKeyboardOn()193 public void autoShow_forwardWithKeyboardOn() { 194 // Create first activity with keyboard on 195 Intent intent1 = 196 createIntent( 197 0x0 /* No window focus flags */, 198 WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED 199 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE, 200 Collections.singletonList(REQUEST_FOCUS_ON_CREATE)); 201 TestActivity firstActivity = TestActivity.start(intent1); 202 // Show Ime with InputMethodManager to ensure the keyboard is on. 203 callOnMainSync(firstActivity::showImeWithInputMethodManager); 204 SystemClock.sleep(1000); 205 mInstrumentation.waitForIdleSync(); 206 207 // Create second activity with parameterized flags: 208 Intent intent2 = 209 createIntent( 210 mWindowFocusFlags, 211 mSoftInputFlags, 212 Collections.singletonList(REQUEST_FOCUS_ON_CREATE)); 213 TestActivity secondActivity = firstActivity.startSecondTestActivity(intent2); 214 215 // The auto-show behavior should be the same as open app 216 verifyAutoShowBehavior_forwardWithKeyboardOn(secondActivity); 217 } 218 219 /** 220 * Test auto-show IME behavior when the activity is navigated back from another activity with 221 * keyboard off. 222 */ 223 @Test autoShow_backwardWithKeyboardOff()224 public void autoShow_backwardWithKeyboardOff() { 225 // Not request focus at onCreate() to avoid triggering auto-show behavior 226 Intent intent1 = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); 227 TestActivity firstActivity = TestActivity.start(intent1); 228 // Request view focus after app starts 229 requestFocusAndVerify(firstActivity); 230 231 Intent intent2 = 232 createIntent( 233 0x0 /* No window focus flags */, 234 WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED 235 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE, 236 Collections.emptyList()); 237 TestActivity secondActivity = firstActivity.startSecondTestActivity(intent2); 238 secondActivity.finish(); 239 mInstrumentation.waitForIdleSync(); 240 241 // When activity is navigated back from another activity with keyboard off, the keyboard 242 // will not show except when soft input visibility flag is SOFT_INPUT_STATE_ALWAYS_VISIBLE. 243 verifyAutoShowBehavior_backwardWithKeyboardOff(firstActivity); 244 } 245 246 /** 247 * Test auto-show IME behavior when the activity is navigated back from another activity with 248 * keyboard on. 249 */ 250 @Test autoShow_backwardWithKeyboardOn()251 public void autoShow_backwardWithKeyboardOn() { 252 // Not request focus at onCreate() to avoid triggering auto-show behavior 253 Intent intent1 = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); 254 TestActivity activity = TestActivity.start(intent1); 255 // Request view focus after app starts 256 requestFocusAndVerify(activity); 257 258 // Create second TestActivity 259 Intent intent2 = 260 createIntent( 261 0x0 /* No window focus flags */, 262 WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED 263 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE, 264 Collections.singletonList(REQUEST_FOCUS_ON_CREATE)); 265 ImeStressTestUtil.TestActivity secondActivity = activity.startSecondTestActivity(intent2); 266 // Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity 267 callOnMainSync(secondActivity::showImeWithInputMethodManager); 268 SystemClock.sleep(1000); 269 mInstrumentation.waitForIdleSync(); 270 // Close the second activity 271 secondActivity.finish(); 272 SystemClock.sleep(1000); 273 mInstrumentation.waitForIdleSync(); 274 // When activity is navigated back from another activity with keyboard on, the keyboard 275 // will not hide except when soft input visibility flag is SOFT_INPUT_STATE_ALWAYS_HIDDEN. 276 verifyAutoShowBehavior_backwardWithKeyboardOn(activity); 277 } 278 279 @Test clickFocusableView_requestFocus()280 public void clickFocusableView_requestFocus() { 281 if ((mWindowFocusFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) { 282 // UiAutomator cannot get UiObject if FLAG_NOT_FOCUSABLE is set 283 return; 284 } 285 Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); 286 TestActivity activity = TestActivity.start(intent); 287 // Request view focus after app starts 288 requestFocusAndVerify(activity); 289 290 // Find the editText and click it 291 UiObject2 editTextUiObject = 292 UiDevice.getInstance(mInstrumentation) 293 .wait(Until.findObject(By.clazz(EditText.class)), 5000); 294 assertThat(editTextUiObject).isNotNull(); 295 editTextUiObject.click(); 296 297 // Ime will show unless window flag is set 298 verifyClickBehavior(activity); 299 } 300 301 @Test clickFocusableView_notRequestFocus()302 public void clickFocusableView_notRequestFocus() { 303 if ((mWindowFocusFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) { 304 // UiAutomator cannot get UiObject if FLAG_NOT_FOCUSABLE is set 305 return; 306 } 307 // Not request focus 308 Intent intent1 = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); 309 TestActivity activity = TestActivity.start(intent1); 310 311 // Find the editText and click it 312 UiObject2 editTextUiObject = 313 UiDevice.getInstance(mInstrumentation) 314 .wait(Until.findObject(By.clazz(EditText.class)), 5000); 315 assertThat(editTextUiObject).isNotNull(); 316 editTextUiObject.click(); 317 318 // Ime will show unless window flag is set 319 verifyClickBehavior(activity); 320 } 321 verifyAutoShowBehavior_forwardWithKeyboardOff(TestActivity activity)322 private void verifyAutoShowBehavior_forwardWithKeyboardOff(TestActivity activity) { 323 if (hasUnfocusableWindowFlags(activity)) { 324 verifyImeAlwaysHiddenWithWindowFlagSet(activity); 325 return; 326 } 327 328 int softInputMode = activity.getWindow().getAttributes().softInputMode; 329 int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE; 330 int softInputAdjustment = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; 331 EditText editText = activity.getEditText(); 332 333 verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true); 334 switch (softInputVisibility) { 335 case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE: 336 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: { 337 // IME will be auto-shown if softInputMode is set with flag: 338 // SOFT_INPUT_STATE_VISIBLE or SOFT_INPUT_STATE_ALWAYS_VISIBLE 339 waitOnMainUntilImeIsShown(editText); 340 break; 341 } 342 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED: 343 case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN: 344 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: { 345 // IME will be not be auto-shown if softInputMode is set with flag: 346 // SOFT_INPUT_STATE_HIDDEN or SOFT_INPUT_STATE_ALWAYS_HIDDEN, 347 // or stay unchanged if set SOFT_INPUT_STATE_UNCHANGED 348 verifyImeIsAlwaysHidden(editText); 349 break; 350 } 351 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: { 352 if ((softInputAdjustment 353 == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) || mIsLargeScreen) { 354 // The current system behavior will choose to show IME automatically when 355 // navigating forward to an app that has no visibility state specified 356 // (i.e. SOFT_INPUT_STATE_UNSPECIFIED) with set SOFT_INPUT_ADJUST_RESIZE 357 // flag or running on a large screen device. 358 waitOnMainUntilImeIsShown(editText); 359 } else { 360 verifyImeIsAlwaysHidden(editText); 361 } 362 break; 363 } 364 default: 365 break; 366 } 367 } 368 verifyAutoShowBehavior_forwardWithKeyboardOn(TestActivity activity)369 private void verifyAutoShowBehavior_forwardWithKeyboardOn(TestActivity activity) { 370 int windowFlags = activity.getWindow().getAttributes().flags; 371 int softInputMode = activity.getWindow().getAttributes().softInputMode; 372 int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE; 373 int softInputAdjustment = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; 374 EditText editText = activity.getEditText(); 375 376 if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) { 377 // When FLAG_NOT_FOCUSABLE is set true, the view will never gain window focus. The IME 378 // will always be hidden even though the view can get focus itself. 379 verifyWindowAndViewFocus( 380 editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ true); 381 // TODO(b/252192121): Ime should be hidden but is shown. 382 // waitOnMainUntilImeIsHidden(editText); 383 return; 384 } else if ((windowFlags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0 385 || (windowFlags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0) { 386 // When FLAG_ALT_FOCUSABLE_IM or FLAG_LOCAL_FOCUS_MODE is set, the view can gain both 387 // window focus and view focus but not IME focus. The IME will always be hidden. 388 verifyWindowAndViewFocus( 389 editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true); 390 // TODO(b/252192121): Ime should be hidden but is shown. 391 // waitOnMainUntilImeIsHidden(editText); 392 return; 393 } 394 verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true); 395 switch (softInputVisibility) { 396 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED: 397 case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE: 398 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: { 399 // IME will be auto-shown if softInputMode is set with flag: 400 // SOFT_INPUT_STATE_VISIBLE or SOFT_INPUT_STATE_ALWAYS_VISIBLE 401 waitOnMainUntilImeIsShown(editText); 402 break; 403 } 404 case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN: 405 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: { 406 // IME will be not be auto-shown if softInputMode is set with flag: 407 // SOFT_INPUT_STATE_HIDDEN or SOFT_INPUT_STATE_ALWAYS_HIDDEN 408 // or stay unchanged if set SOFT_INPUT_STATE_UNCHANGED 409 verifyImeIsAlwaysHidden(editText); 410 break; 411 } 412 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: { 413 if ((softInputAdjustment 414 == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) || mIsLargeScreen) { 415 // The current system behavior will choose to show IME automatically when 416 // navigating forward to an app that has no visibility state specified (i.e. 417 // SOFT_INPUT_STATE_UNSPECIFIED) with set SOFT_INPUT_ADJUST_RESIZE flag or 418 // running on a large screen device. 419 waitOnMainUntilImeIsShown(editText); 420 } else { 421 verifyImeIsAlwaysHidden(editText); 422 } 423 break; 424 } 425 default: 426 break; 427 } 428 } 429 verifyAutoShowBehavior_backwardWithKeyboardOff(TestActivity activity)430 private static void verifyAutoShowBehavior_backwardWithKeyboardOff(TestActivity activity) { 431 if (hasUnfocusableWindowFlags(activity)) { 432 verifyImeAlwaysHiddenWithWindowFlagSet(activity); 433 return; 434 } 435 int softInputMode = activity.getWindow().getAttributes().softInputMode; 436 int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE; 437 EditText editText = activity.getEditText(); 438 439 verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true); 440 if (softInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) { 441 waitOnMainUntilImeIsShown(editText); 442 } else { 443 verifyImeIsAlwaysHidden(editText); 444 } 445 } 446 verifyAutoShowBehavior_backwardWithKeyboardOn(TestActivity activity)447 private static void verifyAutoShowBehavior_backwardWithKeyboardOn(TestActivity activity) { 448 if (hasUnfocusableWindowFlags(activity)) { 449 verifyImeAlwaysHiddenWithWindowFlagSet(activity); 450 return; 451 } 452 int softInputMode = activity.getWindow().getAttributes().softInputMode; 453 int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE; 454 EditText editText = activity.getEditText(); 455 456 verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true); 457 if (softInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) { 458 verifyImeIsAlwaysHidden(editText); 459 } else { 460 waitOnMainUntilImeIsShown(editText); 461 } 462 } 463 verifyClickBehavior(TestActivity activity)464 private static void verifyClickBehavior(TestActivity activity) { 465 int windowFlags = activity.getWindow().getAttributes().flags; 466 EditText editText = activity.getEditText(); 467 468 verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true); 469 if ((windowFlags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0 470 || (windowFlags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0) { 471 verifyImeIsAlwaysHidden(editText); 472 } else { 473 waitOnMainUntilImeIsShown(editText); 474 } 475 } 476 } 477