1 /* 2 * Copyright (C) 2018 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.inputmethod.cts; 18 19 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 20 import static android.inputmethodservice.InputMethodService.FINISH_INPUT_NO_FALLBACK_CONNECTION; 21 import static android.view.Display.DEFAULT_DISPLAY; 22 import static android.view.View.VISIBLE; 23 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 24 import static android.view.WindowInsets.Type.ime; 25 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; 26 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN; 27 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE; 28 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN; 29 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED; 30 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED; 31 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE; 32 import static android.view.inputmethod.InputMethodManager.CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING; 33 import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeInvisible; 34 import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeVisible; 35 import static android.view.inputmethod.cts.util.TestUtils.getOnMainSync; 36 import static android.view.inputmethod.cts.util.TestUtils.runOnMainSync; 37 import static android.view.inputmethod.cts.util.TestUtils.runOnMainSyncWithRethrowing; 38 39 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; 40 import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher; 41 import static com.android.cts.mockime.ImeEventStreamTestUtils.eventMatcher; 42 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand; 43 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent; 44 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEventWithKeyValue; 45 import static com.android.cts.mockime.ImeEventStreamTestUtils.hideSoftInputMatcher; 46 import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent; 47 import static com.android.cts.mockime.ImeEventStreamTestUtils.showSoftInputMatcher; 48 import static com.android.cts.mockime.ImeEventStreamTestUtils.waitForInputViewLayoutStable; 49 import static com.android.cts.mockime.ImeEventStreamTestUtils.withDescription; 50 51 import static org.junit.Assert.assertEquals; 52 import static org.junit.Assert.assertFalse; 53 import static org.junit.Assert.assertNotNull; 54 import static org.junit.Assert.assertNull; 55 import static org.junit.Assert.assertTrue; 56 import static org.junit.Assert.fail; 57 import static org.junit.Assume.assumeFalse; 58 import static org.junit.Assume.assumeNotNull; 59 import static org.junit.Assume.assumeTrue; 60 61 import android.app.AlertDialog; 62 import android.app.Dialog; 63 import android.app.Instrumentation; 64 import android.app.Notification; 65 import android.app.NotificationChannel; 66 import android.app.NotificationManager; 67 import android.app.compat.CompatChanges; 68 import android.content.Context; 69 import android.content.Intent; 70 import android.content.pm.PackageManager; 71 import android.content.res.Resources; 72 import android.graphics.Color; 73 import android.os.SystemClock; 74 import android.os.UserHandle; 75 import android.platform.test.annotations.AppModeFull; 76 import android.platform.test.annotations.AppModeInstant; 77 import android.platform.test.annotations.AppModeSdkSandbox; 78 import android.platform.test.annotations.RequiresFlagsDisabled; 79 import android.platform.test.annotations.RequiresFlagsEnabled; 80 import android.platform.test.flag.junit.CheckFlagsRule; 81 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 82 import android.server.wm.CtsWindowInfoUtils; 83 import android.server.wm.WindowManagerState; 84 import android.text.TextUtils; 85 import android.util.Log; 86 import android.util.Pair; 87 import android.view.Gravity; 88 import android.view.KeyEvent; 89 import android.view.View; 90 import android.view.ViewTreeObserver; 91 import android.view.WindowInsets; 92 import android.view.WindowInsetsController; 93 import android.view.WindowManager; 94 import android.view.inputmethod.Flags; 95 import android.view.inputmethod.InputMethod; 96 import android.view.inputmethod.InputMethodManager; 97 import android.view.inputmethod.cts.util.AutoCloseableWrapper; 98 import android.view.inputmethod.cts.util.EndToEndImeTestBase; 99 import android.view.inputmethod.cts.util.MockTestActivityUtil; 100 import android.view.inputmethod.cts.util.RequireImeCompatFlagRule; 101 import android.view.inputmethod.cts.util.TestActivity; 102 import android.view.inputmethod.cts.util.TestActivity2; 103 import android.view.inputmethod.cts.util.TestUtils; 104 import android.view.inputmethod.cts.util.TestWebView; 105 import android.view.inputmethod.cts.util.UnlockScreenRule; 106 import android.widget.EditText; 107 import android.widget.LinearLayout; 108 import android.widget.PopupWindow; 109 import android.widget.TextView; 110 import android.window.OnBackInvokedDispatcher; 111 112 import androidx.annotation.ColorInt; 113 import androidx.annotation.NonNull; 114 import androidx.test.filters.FlakyTest; 115 import androidx.test.filters.MediumTest; 116 import androidx.test.platform.app.InstrumentationRegistry; 117 import androidx.test.uiautomator.By; 118 import androidx.test.uiautomator.BySelector; 119 import androidx.test.uiautomator.UiDevice; 120 import androidx.test.uiautomator.UiObject2; 121 import androidx.test.uiautomator.Until; 122 123 import com.android.bedstead.multiuser.annotations.RequireNotVisibleBackgroundUsers; 124 import com.android.compatibility.common.util.SystemUtil; 125 import com.android.compatibility.common.util.UserHelper; 126 import com.android.cts.input.UinputTouchScreen; 127 import com.android.cts.mockime.ImeEvent; 128 import com.android.cts.mockime.ImeEventStream; 129 import com.android.cts.mockime.ImeEventStreamTestUtils.DescribedPredicate; 130 import com.android.cts.mockime.ImeLayoutInfo; 131 import com.android.cts.mockime.ImeSettings; 132 import com.android.cts.mockime.MockImeSession; 133 134 import org.junit.Before; 135 import org.junit.Rule; 136 import org.junit.Test; 137 import org.junit.function.ThrowingRunnable; 138 139 import java.io.IOException; 140 import java.time.Duration; 141 import java.util.ArrayList; 142 import java.util.List; 143 import java.util.Map; 144 import java.util.concurrent.CountDownLatch; 145 import java.util.concurrent.TimeUnit; 146 import java.util.concurrent.TimeoutException; 147 import java.util.concurrent.atomic.AtomicBoolean; 148 import java.util.concurrent.atomic.AtomicInteger; 149 import java.util.concurrent.atomic.AtomicReference; 150 import java.util.function.Predicate; 151 152 @MediumTest 153 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).") 154 public final class KeyboardVisibilityControlTest extends EndToEndImeTestBase { 155 @Rule 156 public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); 157 private static final String TAG = KeyboardVisibilityControlTest.class.getSimpleName(); 158 private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(6); 159 private static final long START_INPUT_TIMEOUT = TimeUnit.SECONDS.toMillis(10); 160 private static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(1); 161 private static final long LAYOUT_STABLE_THRESHOLD = TimeUnit.SECONDS.toMillis(3); 162 163 private static final int NEW_KEYBOARD_HEIGHT = 400; 164 private static final PreBackPressProcedure NO_OP_PRE_BACK_PRESS_PROCEDURE = 165 (instrumentation, editorRef) -> {}; 166 167 private static final String DISABLE_AUTO_ROTATE_CMD = 168 "settings put system accelerometer_rotation 0"; 169 private static final String ENABLE_AUTO_ROTATE_CMD = 170 "settings put system accelerometer_rotation 1"; 171 172 private static final String FIXED_TO_USER_ROTATION_CMD = "cmd window fixed-to-user-rotation"; 173 174 175 @Rule 176 public final UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule(); 177 @Rule 178 public final RequireImeCompatFlagRule mRequireImeCompatFlagRule = new RequireImeCompatFlagRule( 179 FINISH_INPUT_NO_FALLBACK_CONNECTION, true); 180 181 private Instrumentation mInstrumentation; 182 183 private TestActivity mTestActivity; 184 185 private final UserHelper mUserHelper = new UserHelper(); 186 187 @Before setup()188 public void setup() { 189 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 190 } 191 onFinishInputViewMatcher(boolean expectedFinishingInput)192 private static DescribedPredicate<ImeEvent> onFinishInputViewMatcher(boolean expectedFinishingInput) { 193 Predicate<ImeEvent> matcher = event -> { 194 if (!TextUtils.equals("onFinishInputView", event.getEventName())) { 195 return false; 196 } 197 final boolean finishingInput = event.getArguments().getBoolean("finishingInput"); 198 return finishingInput == expectedFinishingInput; 199 }; 200 return withDescription("onFinishInputView(finishingInput=" + expectedFinishingInput + ")", 201 matcher); 202 } 203 launchTestActivity(@onNull String focusedMarker, @NonNull String nonFocusedMarker)204 private Pair<EditText, EditText> launchTestActivity(@NonNull String focusedMarker, 205 @NonNull String nonFocusedMarker) { 206 final AtomicReference<EditText> focusedEditTextRef = new AtomicReference<>(); 207 final AtomicReference<EditText> nonFocusedEditTextRef = new AtomicReference<>(); 208 final var activityStarter = 209 new TestActivity.Starter().withDisplayId(mUserHelper.getMainDisplayId()); 210 mTestActivity = activityStarter.startSync(activity -> { 211 final LinearLayout layout = new LinearLayout(activity); 212 layout.setOrientation(LinearLayout.VERTICAL); 213 214 final EditText focusedEditText = new EditText(activity); 215 focusedEditText.setHint("focused editText"); 216 focusedEditText.setPrivateImeOptions(focusedMarker); 217 focusedEditText.requestFocus(); 218 focusedEditTextRef.set(focusedEditText); 219 layout.addView(focusedEditText); 220 221 final EditText nonFocusedEditText = new EditText(activity); 222 nonFocusedEditText.setPrivateImeOptions(nonFocusedMarker); 223 nonFocusedEditText.setHint("target editText"); 224 nonFocusedEditTextRef.set(nonFocusedEditText); 225 layout.addView(nonFocusedEditText); 226 return layout; 227 }, TestActivity.class); 228 return new Pair<>(focusedEditTextRef.get(), nonFocusedEditTextRef.get()); 229 } 230 launchTestActivity(@onNull String marker)231 private EditText launchTestActivity(@NonNull String marker) { 232 return launchTestActivity(marker, getTestMarker(NON_FOCUSED_EDIT_TEXT_TAG)).first; 233 } 234 launchTestActivity2(@onNull String marker)235 private EditText launchTestActivity2(@NonNull String marker) { 236 final AtomicReference<EditText> focusedEditTextRef = new AtomicReference<>(); 237 mTestActivity = new TestActivity.Starter().startSync(activity -> { 238 final LinearLayout layout = new LinearLayout(activity); 239 layout.setOrientation(LinearLayout.VERTICAL); 240 241 final EditText focusedEditText = new EditText(activity); 242 focusedEditText.setHint("focused editText"); 243 focusedEditText.setPrivateImeOptions(marker); 244 focusedEditText.requestFocus(); 245 focusedEditTextRef.set(focusedEditText); 246 layout.addView(focusedEditText); 247 return layout; 248 }, TestActivity2.class); 249 return focusedEditTextRef.get(); 250 } 251 252 @Test testBasicShowHideSoftInput()253 public void testBasicShowHideSoftInput() throws Exception { 254 try (MockImeSession imeSession = MockImeSession.create( 255 mInstrumentation.getContext(), 256 mInstrumentation.getUiAutomation(), 257 new ImeSettings.Builder())) { 258 final ImeEventStream stream = imeSession.openEventStream(); 259 260 final String marker = getTestMarker(FOCUSED_EDIT_TEXT_TAG); 261 final EditText editText = launchTestActivity(marker); 262 263 expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT); 264 notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 265 expectImeInvisible(TIMEOUT); 266 267 final InputMethodManager imm = mTestActivity.getSystemService(InputMethodManager.class); 268 assertTrue("hasActiveInputConnection() must return true if the View has IME focus", 269 getOnMainSync(() -> imm.hasActiveInputConnection(editText))); 270 271 // Test showSoftInput() flow 272 assertTrue("showSoftInput must success if the View has IME focus", 273 getOnMainSync(() -> imm.showSoftInput(editText, 0))); 274 275 expectEvent(stream, showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT); 276 expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 277 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", 278 View.VISIBLE, TIMEOUT); 279 expectImeVisible(TIMEOUT); 280 281 // Test hideSoftInputFromWindow() flow 282 assertTrue("hideSoftInputFromWindow must success if the View has IME focus", 283 getOnMainSync(() -> imm.hideSoftInputFromWindow(editText.getWindowToken(), 0))); 284 285 expectEvent(stream, hideSoftInputMatcher(), TIMEOUT); 286 expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT); 287 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", 288 View.GONE, TIMEOUT); 289 expectImeInvisible(TIMEOUT); 290 } 291 } 292 293 @FunctionalInterface 294 private interface PreBackPressProcedure { run( Instrumentation instrumentation, AtomicReference<EditText> editorRef)295 void run( 296 Instrumentation instrumentation, 297 AtomicReference<EditText> editorRef) throws Exception; 298 } 299 verifyHideImeBackPressed( boolean appRequestsBackCallback, boolean imeRequestsBackCallback, @NonNull PreBackPressProcedure preBackPressProcedure)300 private void verifyHideImeBackPressed( 301 boolean appRequestsBackCallback, boolean imeRequestsBackCallback, 302 @NonNull PreBackPressProcedure preBackPressProcedure) throws Exception { 303 final Instrumentation instrumentation = mInstrumentation; 304 final Context context = instrumentation.getTargetContext(); 305 306 // Whether 'OnBackInvokedCallback' or 'onBackPressed' (legacy back) is used is defined by 307 // the 'enableOnBackInvokedCallback' flag in the Application manifest. 308 // Registering a callback is only authorized if the flag is set to true. Since the 309 // WindowOnBackDispatcher is created at the same time as the ViewRootImpl, for test purpose, 310 // we need to manually set the flag on ApplicationInfo before the window is created which 311 // happens during the MockIme creation and TestActivity creation. 312 final boolean onBackCallbackEnabled = 313 context.getApplicationInfo().isOnBackInvokedCallbackEnabled(); 314 315 try (MockImeSession imeSession = MockImeSession.create( 316 instrumentation.getContext(), 317 instrumentation.getUiAutomation(), 318 new ImeSettings.Builder() 319 .setOnBackCallbackEnabled(imeRequestsBackCallback) 320 )) { 321 final ImeEventStream stream = imeSession.openEventStream(); 322 final String marker = getTestMarker(FOCUSED_EDIT_TEXT_TAG); 323 final AtomicInteger backCallbackInvocationCount = new AtomicInteger(); 324 325 if (appRequestsBackCallback) { 326 context.getApplicationInfo().setEnableOnBackInvokedCallback(true); 327 } 328 329 final EditText editText = launchTestActivity(marker); 330 final AtomicReference<EditText> editorRef = new AtomicReference<>(); 331 editorRef.set(editText); 332 final TestActivity testActivity = (TestActivity) editText.getContext(); 333 334 if (appRequestsBackCallback) { 335 testActivity.getOnBackInvokedDispatcher().registerOnBackInvokedCallback( 336 OnBackInvokedDispatcher.PRIORITY_DEFAULT, () -> { 337 backCallbackInvocationCount.getAndIncrement(); 338 }); 339 } else { 340 testActivity.setIgnoreBackKey(true); 341 } 342 343 expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT); 344 notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 345 expectImeInvisible(TIMEOUT); 346 347 final InputMethodManager imm = testActivity.getSystemService(InputMethodManager.class); 348 assertTrue("hasActiveInputConnection() must return true if the View has IME focus", 349 getOnMainSync(() -> imm.hasActiveInputConnection(editText))); 350 351 // Test showSoftInput() flow 352 assertTrue("showSoftInput must success if the View has IME focus", 353 getOnMainSync(() -> imm.showSoftInput(editText, 0))); 354 355 expectEvent(stream, showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT); 356 expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 357 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", 358 View.VISIBLE, TIMEOUT); 359 expectImeVisible(TIMEOUT); 360 361 preBackPressProcedure.run(instrumentation, editorRef); 362 363 // Pressing back key, expect soft-keyboard will become invisible. 364 instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK); 365 expectEvent(stream, hideSoftInputMatcher(), TIMEOUT); 366 expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT); 367 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", 368 View.GONE, TIMEOUT); 369 expectImeInvisible(TIMEOUT); 370 371 if (appRequestsBackCallback) { 372 // Verify that IME callback is removed after IME is hidden. 373 instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK); 374 assertEquals(1, backCallbackInvocationCount.get()); 375 } 376 } finally { 377 context.getApplicationInfo().setEnableOnBackInvokedCallback(onBackCallbackEnabled); 378 } 379 } 380 381 @Test testHideImeAfterBackPressed_legacyAppLegacyIme()382 public void testHideImeAfterBackPressed_legacyAppLegacyIme() throws Exception { 383 verifyHideImeBackPressed(false /* appRequestsBackCallback */, 384 false /* imeRequestsBackCallback */, 385 (instrumentation, editorRef) -> {} /* pre back press procedure */); 386 } 387 388 @Test testHideImeAfterBackPressed_migratedAppLegacyIme()389 public void testHideImeAfterBackPressed_migratedAppLegacyIme() throws Exception { 390 verifyHideImeBackPressed(true /* appRequestsBackCallback */, 391 false /* imeRequestsBackCallback */, 392 NO_OP_PRE_BACK_PRESS_PROCEDURE); 393 } 394 395 @Test testHideImeAfterBackPressed_migratedAppMigratedIme()396 public void testHideImeAfterBackPressed_migratedAppMigratedIme() throws Exception { 397 verifyHideImeBackPressed(true /* appRequestsBackCallback */, 398 true /* imeRequestsBackCallback */, 399 NO_OP_PRE_BACK_PRESS_PROCEDURE); 400 } 401 402 @Test testHideImeAfterBackPressed_legacyAppMigratedIme()403 public void testHideImeAfterBackPressed_legacyAppMigratedIme() throws Exception { 404 verifyHideImeBackPressed(true /* appRequestsBackCallback */, 405 true /* imeRequestsBackCallback */, 406 NO_OP_PRE_BACK_PRESS_PROCEDURE); 407 } 408 409 @RequireNotVisibleBackgroundUsers(reason = 410 "Background visible user devices (primarily Android auto) currently doesn't support " 411 + "per display interactiveness. So when the screen Off event is sent, " 412 + "PowerManager#IsInteractive is still true while driver screen is off as passenger " 413 + "screens are on. It also doesn't trigger the code path related to global " 414 + "wakefulness in power manager. The test will be enabled once per display " 415 + "interactiveness is supported and power manager to IME communication is enabled on " 416 + "partial interactiveness. relevant bugs: b/330610015 b/366045308 b/366037029") 417 @AppModeFull(reason = "KeyguardManager is not accessible from instant apps") 418 @Test testHideImeAfterBackPressed_ScreenOffOn()419 public void testHideImeAfterBackPressed_ScreenOffOn() throws Exception { 420 verifyHideImeBackPressed(true /* appRequestsBackCallback */, 421 true /* imeRequestsBackCallback */, 422 (instrumentation, editorRef) -> { 423 TestUtils.turnScreenOff(); 424 TestUtils.waitOnMainUntil( 425 () -> ((TestActivity) editorRef.get().getContext()).isPaused(), 426 TIMEOUT); 427 TestUtils.turnScreenOn(); 428 TestUtils.unlockScreen(); 429 TestUtils.waitOnMainUntil( 430 () -> !((TestActivity) editorRef.get().getContext()).isPaused(), 431 TIMEOUT); 432 // Before testing the back procedure, ensure the test activity has the window 433 // focus and the IME visible after screen-on. 434 TestUtils.waitOnMainUntil(editorRef.get()::hasWindowFocus, TIMEOUT); 435 expectImeVisible(TIMEOUT); 436 } /* pre back press procedure */); 437 } 438 439 @Test testHideImeAfterBackPressed_rootViewChanges()440 public void testHideImeAfterBackPressed_rootViewChanges() throws Exception { 441 verifyHideImeBackPressed(true /* appRequestsBackCallback */, 442 true /* imeRequestsBackCallback */, 443 (instrumentation, editorRef) -> { 444 AutoCloseableWrapper<Dialog> dialogWrapper = 445 createDialogWrapper(editorRef.get()); 446 instrumentation.waitForIdleSync(); 447 // Verify IME became invisible when the Dialog is shown. 448 // Note: the IME is partially visible behind dimmed layer and it won't match 449 // the screenshot. 450 expectImeInvisible(NOT_EXPECT_TIMEOUT); 451 452 runOnMainSync(() -> dialogWrapper.get().dismiss()); 453 // Verify IME became visible when the Dialog has dismissed. 454 expectImeVisible(TIMEOUT); 455 } /* pre back press procedure */); 456 } 457 458 @Test testShowHideSoftInputShouldBeIgnoredOnNonFocusedView()459 public void testShowHideSoftInputShouldBeIgnoredOnNonFocusedView() throws Exception { 460 final InputMethodManager imm = mInstrumentation 461 .getTargetContext().getSystemService(InputMethodManager.class); 462 463 try (MockImeSession imeSession = MockImeSession.create( 464 mInstrumentation.getContext(), 465 mInstrumentation.getUiAutomation(), 466 new ImeSettings.Builder())) { 467 final ImeEventStream stream = imeSession.openEventStream(); 468 469 final String focusedMarker = getTestMarker(FOCUSED_EDIT_TEXT_TAG); 470 final String nonFocusedMarker = getTestMarker(NON_FOCUSED_EDIT_TEXT_TAG); 471 final Pair<EditText, EditText> editTextPair = 472 launchTestActivity(focusedMarker, nonFocusedMarker); 473 final EditText nonFocusedEditText = editTextPair.second; 474 475 expectEvent(stream, editorMatcher("onStartInput", focusedMarker), TIMEOUT); 476 477 expectImeInvisible(TIMEOUT); 478 assertFalse("hasActiveInputConnection() must return false if the View does not have IME" 479 + " focus", 480 getOnMainSync(() -> imm.hasActiveInputConnection(nonFocusedEditText))); 481 assertFalse("showSoftInput must fail if the View does not have IME focus", 482 getOnMainSync(() -> imm.showSoftInput(nonFocusedEditText, 0))); 483 notExpectEvent(stream, showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT); 484 485 getOnMainSync(() -> imm.hideSoftInputFromWindow( 486 nonFocusedEditText.getWindowToken(), 0)); 487 // IME was never shown, so there should be no hideSoftInput. 488 notExpectEvent(stream, hideSoftInputMatcher(), TIMEOUT); 489 expectImeInvisible(TIMEOUT); 490 } 491 } 492 493 @Test testToggleSoftInput()494 public void testToggleSoftInput() throws Exception { 495 try (MockImeSession imeSession = MockImeSession.create( 496 mInstrumentation.getContext(), 497 mInstrumentation.getUiAutomation(), 498 new ImeSettings.Builder())) { 499 final ImeEventStream stream = imeSession.openEventStream(); 500 501 final String marker = getTestMarker(FOCUSED_EDIT_TEXT_TAG); 502 final EditText editText = launchTestActivity(marker); 503 504 expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT); 505 notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 506 expectImeInvisible(TIMEOUT); 507 508 // Test toggleSoftInputFromWindow() flow 509 final InputMethodManager imm = mTestActivity.getSystemService(InputMethodManager.class); 510 runOnMainSync(() -> imm.toggleSoftInputFromWindow(editText.getWindowToken(), 0, 0)); 511 512 expectEvent(stream.copy(), showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT); 513 expectEvent(stream.copy(), editorMatcher("onStartInputView", marker), TIMEOUT); 514 expectImeVisible(TIMEOUT); 515 516 // Calling toggleSoftInputFromWindow() must hide the IME. 517 runOnMainSync(() -> imm.toggleSoftInputFromWindow(editText.getWindowToken(), 0, 0)); 518 519 expectEvent(stream, hideSoftInputMatcher(), TIMEOUT); 520 expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT); 521 expectImeInvisible(TIMEOUT); 522 } 523 } 524 525 @Test 526 @FlakyTest(bugId = 294840051) testShowHideKeyboardOnWebView()527 public void testShowHideKeyboardOnWebView() throws Exception { 528 final PackageManager pm = 529 mInstrumentation.getContext().getPackageManager(); 530 assumeTrue(pm.hasSystemFeature("android.software.webview")); 531 532 try (MockImeSession imeSession = MockImeSession.create( 533 mInstrumentation.getContext(), 534 mInstrumentation.getUiAutomation(), 535 new ImeSettings.Builder())) { 536 final ImeEventStream stream = imeSession.openEventStream(); 537 final String marker = getTestMarker(); 538 final UiObject2 inputTextField = TestWebView.launchTestWebViewActivity( 539 TIMEOUT, marker); 540 assertNotNull("Editor must exists on WebView", inputTextField); 541 expectImeInvisible(TIMEOUT); 542 543 inputTextField.click(); 544 expectEvent(stream.copy(), showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT); 545 expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT); 546 expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 547 expectImeVisible(TIMEOUT); 548 } 549 } 550 551 @Test 552 @FlakyTest(detail = "slow test") testShowHideKeyboardWithInterval()553 public void testShowHideKeyboardWithInterval() throws Exception { 554 final InputMethodManager imm = mInstrumentation 555 .getTargetContext().getSystemService(InputMethodManager.class); 556 557 try (MockImeSession imeSession = MockImeSession.create( 558 mInstrumentation.getContext(), 559 mInstrumentation.getUiAutomation(), 560 new ImeSettings.Builder())) { 561 final ImeEventStream stream = imeSession.openEventStream(); 562 final String marker = getTestMarker(FOCUSED_EDIT_TEXT_TAG); 563 final EditText editText = launchTestActivity(marker); 564 expectImeInvisible(TIMEOUT); 565 566 runOnMainSync(() -> imm.showSoftInput(editText, 0)); 567 expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 568 expectImeVisible(TIMEOUT); 569 570 // Intervals = 10, 20, 30, ..., 100, 150, 200, ... 571 final List<Integer> intervals = new ArrayList<>(); 572 for (int i = 10; i < 100; i += 10) intervals.add(i); 573 for (int i = 100; i < 500; i += 50) intervals.add(i); 574 // Regression test for b/221483132. 575 // WindowInsetsController tries to clean up IME window after IME hide animation is done. 576 // Makes sure that IMM#showSoftInput during IME hide animation cancels the cleanup. 577 for (int intervalMillis : intervals) { 578 runOnMainSync(() -> imm.hideSoftInputFromWindow(editText.getWindowToken(), 0)); 579 SystemClock.sleep(intervalMillis); 580 runOnMainSync(() -> imm.showSoftInput(editText, 0)); 581 expectImeVisible(TIMEOUT, "IME should be visible. Interval = " + intervalMillis); 582 } 583 } 584 } 585 586 /** 587 * Verifies that a hideSoftInputFromWindow call just after a showSoftInput call can succeed. 588 * 589 * <p>This is a regression test for Bug 21727232.</p> 590 */ 591 @Test testShowHideKeyboardImmediately()592 public void testShowHideKeyboardImmediately() throws Exception { 593 try (MockImeSession imeSession = MockImeSession.create( 594 mInstrumentation.getContext(), 595 mInstrumentation.getUiAutomation(), 596 new ImeSettings.Builder())) { 597 final ImeEventStream stream = imeSession.openEventStream(); 598 final String marker = getTestMarker(FOCUSED_EDIT_TEXT_TAG); 599 final EditText editText = launchTestActivity(marker); 600 601 expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT); 602 603 final var imm = mTestActivity.getSystemService(InputMethodManager.class); 604 assertTrue("hasActiveInputConnection() must return true if the View has IME focus", 605 getOnMainSync(() -> imm.hasActiveInputConnection(editText))); 606 607 // Issue a command with no effect to wait until MockIme becomes idle 608 expectCommand(stream, imeSession.callGetWindowLayoutInfo(), TIMEOUT); 609 // Copy event stream to check assertion starting from the start of the stream 610 notExpectEvent(imeSession.openEventStream(), editorMatcher("onStartInputView", marker), 611 0); 612 613 // Test calling showSoftInput() immediately followed by hideSoftInputFromWindow() 614 runOnMainSyncWithRethrowing(() -> { 615 assertTrue("showSoftInput must success if the View has IME focus", 616 imm.showSoftInput(editText, 0 /* flags */)); 617 assertTrue("hideSoftInputFromWindow must success when called right" 618 + " after showSoftInput", 619 imm.hideSoftInputFromWindow(editText.getWindowToken(), 0 /* flags */)); 620 }); 621 622 // Assert showSoftInput() flow was observed 623 expectEvent(stream, showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT); 624 expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 625 626 // Assert hideSoftInputFromWindow() flow was observed 627 expectEvent(stream, hideSoftInputMatcher(), TIMEOUT); 628 expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT); 629 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", 630 View.GONE, TIMEOUT); 631 } 632 } 633 634 @Test 635 @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER) testShowSoftInputWithShowForcedFlagWhenAppIsLeaving()636 public void testShowSoftInputWithShowForcedFlagWhenAppIsLeaving() throws Exception { 637 try (MockImeSession imeSession = MockImeSession.create( 638 mInstrumentation.getContext(), 639 mInstrumentation.getUiAutomation(), 640 new ImeSettings.Builder())) { 641 final ImeEventStream stream = imeSession.openEventStream(); 642 643 // Launch a simple test activity 644 final TestActivity testActivity = TestActivity.startSync(activity -> { 645 activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_HIDDEN); 646 return new LinearLayout(activity); 647 }); 648 TestUtils.waitOnMainUntil(testActivity::hasWindowFocus, TIMEOUT); 649 650 // Launch a test editor activity 651 final String marker = getTestMarker(); 652 final AtomicReference<EditText> ediTextRef = new AtomicReference<>(); 653 final TestActivity testEditorActivity = 654 new TestActivity.Starter().asNewTask().startSync(activity -> { 655 final LinearLayout layout = new LinearLayout(activity); 656 layout.setOrientation(LinearLayout.VERTICAL); 657 658 final EditText focusedEditText = new EditText(activity); 659 focusedEditText.setHint("focused editText"); 660 focusedEditText.setPrivateImeOptions(marker); 661 focusedEditText.requestFocus(); 662 layout.addView(focusedEditText); 663 ediTextRef.set(focusedEditText); 664 return layout; 665 }, TestActivity.class); 666 667 expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT); 668 notExpectEvent(stream, editorMatcher("onStartInputView", marker), NOT_EXPECT_TIMEOUT); 669 expectImeInvisible(TIMEOUT); 670 671 final InputMethodManager imm = testActivity.getSystemService(InputMethodManager.class); 672 assertTrue("hasActiveInputConnection() must return true if the View has IME focus", 673 getOnMainSync(() -> imm.hasActiveInputConnection(ediTextRef.get()))); 674 675 // Test showSoftInput() flow with adding SHOW_FORCED flag 676 assertTrue("showSoftInput must success if the View has IME focus", 677 getOnMainSync(() -> 678 imm.showSoftInput(ediTextRef.get(), InputMethodManager.SHOW_FORCED))); 679 680 expectEvent(stream, showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT); 681 expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 682 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", 683 View.VISIBLE, TIMEOUT); 684 expectImeVisible(TIMEOUT); 685 686 // Finish testEditorActivity 687 runOnMainSync(testEditorActivity::finish); 688 689 // Verify soft-keyboard will not visible when enabling the platform compat flag to 690 // clear SHOW_FOCED flag. Otherwise, keeping the legacy behavior of SHOW_FOCED that 691 // soft-keyboard remains visible if there is no explicit hiding request. 692 if (isClearShowForcedFlagEnabled(testActivity.getPackageName())) { 693 notExpectEvent(stream, eventMatcher("showSoftInput"), 694 NOT_EXPECT_TIMEOUT); 695 expectImeInvisible(TIMEOUT); 696 } else { 697 expectEvent(stream, eventMatcher("showSoftInput"), TIMEOUT); 698 expectImeVisible(TIMEOUT); 699 } 700 } 701 } 702 703 @Test testFloatingImeHideKeyboardAfterBackPressed()704 public void testFloatingImeHideKeyboardAfterBackPressed() throws Exception { 705 final Instrumentation instrumentation = mInstrumentation; 706 707 // Initial MockIme with floating IME settings. 708 try (MockImeSession imeSession = MockImeSession.create( 709 instrumentation.getContext(), instrumentation.getUiAutomation(), 710 getFloatingImeSettings(Color.BLACK))) { 711 final ImeEventStream stream = imeSession.openEventStream(); 712 final String marker = getTestMarker(FOCUSED_EDIT_TEXT_TAG); 713 final EditText editText = launchTestActivity(marker); 714 715 expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT); 716 notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 717 expectImeInvisible(TIMEOUT); 718 719 final InputMethodManager imm = mTestActivity.getSystemService(InputMethodManager.class); 720 assertTrue("hasActiveInputConnection() must return true if the View has IME focus", 721 getOnMainSync(() -> imm.hasActiveInputConnection(editText))); 722 723 // Test showSoftInput() flow 724 assertTrue("showSoftInput must success if the View has IME focus", 725 getOnMainSync(() -> imm.showSoftInput(editText, 0))); 726 727 expectEvent(stream, showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT); 728 expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 729 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", 730 View.VISIBLE, TIMEOUT); 731 expectImeVisible(TIMEOUT); 732 733 // Pressing back key, expect soft-keyboard will become invisible. 734 instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK); 735 expectEvent(stream, hideSoftInputMatcher(), TIMEOUT); 736 expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT); 737 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", 738 View.GONE, TIMEOUT); 739 expectImeInvisible(TIMEOUT); 740 } 741 } 742 743 @Test testImeVisibilityWhenDismissingDialogWithImeFocused()744 public void testImeVisibilityWhenDismissingDialogWithImeFocused() throws Exception { 745 final Instrumentation instrumentation = mInstrumentation; 746 try (MockImeSession imeSession = MockImeSession.create( 747 instrumentation.getContext(), 748 instrumentation.getUiAutomation(), 749 new ImeSettings.Builder())) { 750 final ImeEventStream stream = imeSession.openEventStream(); 751 752 // Launch a simple test activity 753 final TestActivity testActivity = 754 new TestActivity.Starter() 755 .withWindowingMode(WINDOWING_MODE_FULLSCREEN) 756 .startSync(LinearLayout::new, TestActivity.class); 757 758 // Launch a dialog 759 final String marker = getTestMarker(); 760 final AtomicReference<EditText> editTextRef = new AtomicReference<>(); 761 final AtomicReference<AlertDialog> dialogRef = new AtomicReference<>(); 762 TestUtils.runOnMainSync(() -> { 763 final EditText editText = new EditText(testActivity); 764 editText.setHint("focused editText"); 765 editText.setPrivateImeOptions(marker); 766 editText.requestFocus(); 767 final AlertDialog dialog = new AlertDialog.Builder(testActivity) 768 .setView(editText) 769 .create(); 770 final WindowInsetsController.OnControllableInsetsChangedListener listener = 771 new WindowInsetsController.OnControllableInsetsChangedListener() { 772 @Override 773 public void onControllableInsetsChanged( 774 @NonNull WindowInsetsController controller, int typeMask) { 775 if ((typeMask & ime()) != 0) { 776 editText.getWindowInsetsController() 777 .removeOnControllableInsetsChangedListener(this); 778 editText.getWindowInsetsController().show(ime()); 779 } 780 } 781 }; 782 dialog.show(); 783 editText.getWindowInsetsController().addOnControllableInsetsChangedListener( 784 listener); 785 editTextRef.set(editText); 786 dialogRef.set(dialog); 787 }); 788 TestUtils.waitOnMainUntil(() -> dialogRef.get().isShowing() 789 && editTextRef.get().hasFocus(), TIMEOUT); 790 expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT); 791 expectEvent(stream, eventMatcher("showSoftInput"), TIMEOUT); 792 expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 793 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", 794 View.VISIBLE, TIMEOUT); 795 expectImeVisible(TIMEOUT); 796 797 // Hide keyboard and dismiss dialog. 798 TestUtils.runOnMainSync(() -> { 799 editTextRef.get().getWindowInsetsController().hide(ime()); 800 dialogRef.get().dismiss(); 801 }); 802 803 // Expect onFinishInput called and keyboard should hide successfully. 804 expectEvent(stream, hideSoftInputMatcher(), TIMEOUT); 805 expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT); 806 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", 807 View.GONE, TIMEOUT); 808 expectImeInvisible(TIMEOUT); 809 810 // onWindowVisibilityChanged event can be out of sequence. Creating 811 // a copy of the ImeEventStream to handle this event. 812 final ImeEventStream streamCopy = stream.copy(); 813 814 // Expect fallback input connection started and keyboard invisible after activity 815 // focused unless avoidable keyboard startup is desired, 816 // in which case, no fallback will be started. 817 if (!isPreventImeStartup()) { 818 final ImeEvent onStart = expectEvent(stream, eventMatcher("onStartInput"), TIMEOUT); 819 assertTrue(onStart.getEnterState().hasFallbackInputConnection()); 820 } 821 TestUtils.waitOnMainUntil(testActivity::hasWindowFocus, TIMEOUT); 822 expectEventWithKeyValue(streamCopy, "onWindowVisibilityChanged", "visible", 823 View.GONE, TIMEOUT); 824 expectImeInvisible(TIMEOUT); 825 } 826 } 827 828 @RequireNotVisibleBackgroundUsers(reason = 829 "Background visible user devices (primarily Android auto) currently doesn't support " 830 + "per display interactiveness. So when the screen Off event is sent, " 831 + "PowerManager#IsInteractive is still true while driver screen is off as passenger " 832 + "screens are on. It also doesn't trigger the code path related to global " 833 + "wakefulness in power manager. The test will be enabled once per display " 834 + "interactiveness is supported and power manager to IME communication is enabled on " 835 + "partial interactiveness. relevant bugs: b/330610015 b/366045308 b/366037029") 836 @AppModeFull(reason = "KeyguardManager is not accessible from instant apps") 837 @Test testImeState_Unspecified_EditorDialogLostFocusAfterUnlocked()838 public void testImeState_Unspecified_EditorDialogLostFocusAfterUnlocked() throws Exception { 839 runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_UNSPECIFIED); 840 } 841 842 @RequireNotVisibleBackgroundUsers(reason = 843 "Background visible user devices (primarily Android auto) currently doesn't support " 844 + "per display interactiveness. So when the screen Off event is sent, " 845 + "PowerManager#IsInteractive is still true while driver screen is off as passenger " 846 + "screens are on. It also doesn't trigger the code path related to global " 847 + "wakefulness in power manager. The test will be enabled once per display " 848 + "interactiveness is supported and power manager to IME communication is enabled on " 849 + "partial interactiveness. relevant bugs: b/330610015 b/366045308 b/366037029") 850 @AppModeFull(reason = "KeyguardManager is not accessible from instant apps") 851 @Test testImeState_Visible_EditorDialogLostFocusAfterUnlocked()852 public void testImeState_Visible_EditorDialogLostFocusAfterUnlocked() throws Exception { 853 runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_VISIBLE); 854 } 855 856 @RequireNotVisibleBackgroundUsers(reason = 857 "Background visible user devices (primarily Android auto) currently doesn't support " 858 + "per display interactiveness. So when the screen Off event is sent, " 859 + "PowerManager#IsInteractive is still true while driver screen is off as passenger " 860 + "screens are on. It also doesn't trigger the code path related to global " 861 + "wakefulness in power manager. The test will be enabled once per display " 862 + "interactiveness is supported and power manager to IME communication is enabled on " 863 + "partial interactiveness. relevant bugs: b/330610015 b/366045308 b/366037029") 864 @AppModeFull(reason = "KeyguardManager is not accessible from instant apps") 865 @Test testImeState_AlwaysVisible_EditorDialogLostFocusAfterUnlocked()866 public void testImeState_AlwaysVisible_EditorDialogLostFocusAfterUnlocked() throws Exception { 867 runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_ALWAYS_VISIBLE); 868 } 869 870 @RequireNotVisibleBackgroundUsers(reason = 871 "Background visible user devices (primarily Android auto) currently doesn't support " 872 + "per display interactiveness. So when the screen Off event is sent, " 873 + "PowerManager#IsInteractive is still true while driver screen is off as passenger " 874 + "screens are on. It also doesn't trigger the code path related to global " 875 + "wakefulness in power manager. The test will be enabled once per display " 876 + "interactiveness is supported and power manager to IME communication is enabled on " 877 + "partial interactiveness. relevant bugs: b/330610015 b/366045308 b/366037029") 878 @AppModeFull(reason = "KeyguardManager is not accessible from instant apps") 879 @Test testImeState_Hidden_EditorDialogLostFocusAfterUnlocked()880 public void testImeState_Hidden_EditorDialogLostFocusAfterUnlocked() throws Exception { 881 runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_HIDDEN); 882 } 883 884 @RequireNotVisibleBackgroundUsers(reason = 885 "Background visible user devices (primarily Android auto) currently doesn't support " 886 + "per display interactiveness. So when the screen Off event is sent, " 887 + "PowerManager#IsInteractive is still true while driver screen is off as passenger " 888 + "screens are on. It also doesn't trigger the code path related to global " 889 + "wakefulness in power manager. The test will be enabled once per display " 890 + "interactiveness is supported and power manager to IME communication is enabled on " 891 + "partial interactiveness. relevant bugs: b/330610015 b/366045308 b/366037029") 892 @AppModeFull(reason = "KeyguardManager is not accessible from instant apps") 893 @Test testImeState_AlwaysHidden_EditorDialogLostFocusAfterUnlocked()894 public void testImeState_AlwaysHidden_EditorDialogLostFocusAfterUnlocked() throws Exception { 895 runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_ALWAYS_HIDDEN); 896 } 897 runImeDoesntReshowAfterKeyguardTest(int softInputState)898 private void runImeDoesntReshowAfterKeyguardTest(int softInputState) throws Exception { 899 try (MockImeSession imeSession = MockImeSession.create( 900 mInstrumentation.getContext(), 901 mInstrumentation.getUiAutomation(), 902 new ImeSettings.Builder())) { 903 final ImeEventStream stream = imeSession.openEventStream(); 904 // Launch a simple test activity 905 final TestActivity testActivity = 906 new TestActivity.Starter() 907 .withWindowingMode(WINDOWING_MODE_FULLSCREEN) 908 .startSync(LinearLayout::new, TestActivity.class); 909 910 // Launch a dialog and show keyboard 911 final String marker = getTestMarker(); 912 final AtomicReference<EditText> editTextRef = new AtomicReference<>(); 913 final AtomicReference<AlertDialog> dialogRef = new AtomicReference<>(); 914 TestUtils.runOnMainSync(() -> { 915 final EditText editText = new EditText(testActivity); 916 editText.setHint("focused editText"); 917 editText.setPrivateImeOptions(marker); 918 editText.requestFocus(); 919 final AlertDialog dialog = new AlertDialog.Builder(testActivity) 920 .setView(editText) 921 .create(); 922 dialog.getWindow().setSoftInputMode(softInputState); 923 // Tracking onFocusChange callback for debugging purpose. 924 editText.setOnFocusChangeListener((v, hasFocus) -> { 925 if (Log.isLoggable(TAG, Log.VERBOSE)) { 926 Log.v(TAG, "Editor " + editText + " hasFocus=" + hasFocus, new Throwable()); 927 } 928 }); 929 dialog.show(); 930 editText.getWindowInsetsController().show(ime()); 931 editTextRef.set(editText); 932 dialogRef.set(dialog); 933 }); 934 935 try (AutoCloseableWrapper<AlertDialog> dialogCloseWrapper = AutoCloseableWrapper.create( 936 dialogRef.get(), dialog -> TestUtils.runOnMainSync(dialog::dismiss))) { 937 TestUtils.waitOnMainUntil(() -> dialogRef.get().isShowing() 938 && editTextRef.get().hasFocus(), TIMEOUT); 939 expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT); 940 expectEvent(stream, eventMatcher("showSoftInput"), TIMEOUT); 941 // Copy the event stream to verify both events in case expectEvent missed the 942 // event verification if the actual event sequence has flipped. 943 expectEvent(stream.copy(), editorMatcher("onStartInputView", marker), TIMEOUT); 944 expectEventWithKeyValue(stream.copy(), "onWindowVisibilityChanged", "visible", 945 View.VISIBLE, TIMEOUT); 946 expectImeVisible(TIMEOUT); 947 948 TestUtils.turnScreenOff(); 949 // Clear editor focus after screen-off 950 TestUtils.runOnMainSync(editTextRef.get()::clearFocus); 951 952 TestUtils.waitOnMainUntil(() -> editTextRef.get().getWindowVisibility() != VISIBLE, 953 TIMEOUT); 954 expectEvent(stream, onFinishInputViewMatcher(true), TIMEOUT); 955 if (imeSession.isFinishInputNoFallbackConnectionEnabled()) { 956 // When IME enabled the new app compat behavior to finish input without fallback 957 // input connection when device interactive state changed, 958 // we expect onFinishInput happens without any additional fallback input 959 // connection started and no showShowSoftInput requested. 960 expectEvent(stream, eventMatcher("onFinishInput"), 961 TIMEOUT); 962 notExpectEvent(stream, eventMatcher("showSoftInput"), 963 NOT_EXPECT_TIMEOUT); 964 } else { 965 // For legacy IME, the fallback input connection will started after screen-off. 966 expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT); 967 expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 968 // Expect showSoftInput comes when system notify InsetsController to apply 969 // show IME insets after IME input target updated. 970 expectEvent(stream, eventMatcher("showSoftInput"), 971 TIMEOUT); 972 notExpectEvent(stream, hideSoftInputMatcher(), NOT_EXPECT_TIMEOUT); 973 } 974 975 // Verify IME will invisible after device unlocked 976 TestUtils.turnScreenOn(); 977 TestUtils.unlockScreen(); 978 // Expect hideSoftInput will called by IMMS when the same window 979 // focused since the editText view focus has been cleared. 980 TestUtils.waitOnMainUntil(() -> editTextRef.get().hasWindowFocus() 981 && !editTextRef.get().hasFocus(), TIMEOUT); 982 expectEvent(stream, hideSoftInputMatcher(), TIMEOUT); 983 if (!imeSession.isFinishInputNoFallbackConnectionEnabled()) { 984 expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT); 985 } 986 expectImeInvisible(TIMEOUT); 987 } 988 } 989 } 990 991 @AppModeFull 992 @Test testImeVisibilityWhenImeTransitionBetweenActivities_Full()993 public void testImeVisibilityWhenImeTransitionBetweenActivities_Full() throws Exception { 994 runImeVisibilityWhenImeTransitionBetweenActivities(false /* instant */); 995 } 996 997 @AppModeInstant 998 @Test testImeVisibilityWhenImeTransitionBetweenActivities_Instant()999 public void testImeVisibilityWhenImeTransitionBetweenActivities_Instant() throws Exception { 1000 runImeVisibilityWhenImeTransitionBetweenActivities(true /* instant */); 1001 } 1002 1003 @AppModeFull 1004 @Test testImeInvisibleWhenForceStopPkgProcess_Full()1005 public void testImeInvisibleWhenForceStopPkgProcess_Full() throws Exception { 1006 runImeVisibilityTestWhenForceStopPackage(false /* instant */); 1007 } 1008 1009 @AppModeInstant 1010 @Test testImeInvisibleWhenForceStopPkgProcess_Instant()1011 public void testImeInvisibleWhenForceStopPkgProcess_Instant() throws Exception { 1012 runImeVisibilityTestWhenForceStopPackage(true /* instant */); 1013 } 1014 1015 @Test testRestoreImeVisibility()1016 public void testRestoreImeVisibility() throws Exception { 1017 // TODO(b/226110728): Remove after we can send ime restore signal to DisplayAreaOrganizer. 1018 assumeFalse(isImeOrganized(DEFAULT_DISPLAY)); 1019 runRestoreImeVisibility(TestSoftInputMode.UNCHANGED_WITH_BACKWARD_NAV, true); 1020 } 1021 1022 @Test testRestoreImeVisibility_noRestoreForAlwaysHidden()1023 public void testRestoreImeVisibility_noRestoreForAlwaysHidden() throws Exception { 1024 runRestoreImeVisibility(TestSoftInputMode.ALWAYS_HIDDEN_WITH_BACKWARD_NAV, false); 1025 } 1026 1027 @Test testRestoreImeVisibility_noRestoreForHiddenWithForwardNav()1028 public void testRestoreImeVisibility_noRestoreForHiddenWithForwardNav() throws Exception { 1029 runRestoreImeVisibility(TestSoftInputMode.HIDDEN_WITH_FORWARD_NAV, false); 1030 } 1031 1032 /** 1033 * Test case for Bug 225028378. 1034 * 1035 * <p>This test ensures that showing a non-ime-focusable {@link PopupWindow} with 1036 * {@link PopupWindow#INPUT_METHOD_NOT_NEEDED} will be on top of the IME.</p> 1037 */ 1038 @Test testNonImeFocusablePopupWindow_onTopOfIme()1039 public void testNonImeFocusablePopupWindow_onTopOfIme() throws Exception { 1040 final Instrumentation instrumentation = mInstrumentation; 1041 try (MockImeSession imeSession = MockImeSession.create( 1042 mInstrumentation.getContext(), 1043 mInstrumentation.getUiAutomation(), 1044 new ImeSettings.Builder())) { 1045 final ImeEventStream stream = imeSession.openEventStream(); 1046 final String marker = getTestMarker(); 1047 final AtomicReference<EditText> editorRef = new AtomicReference<>(); 1048 new TestActivity.Starter().withWindowingMode( 1049 WINDOWING_MODE_FULLSCREEN).startSync(activity -> { 1050 final LinearLayout layout = new LinearLayout(activity); 1051 layout.setOrientation(LinearLayout.VERTICAL); 1052 layout.setGravity(Gravity.BOTTOM); 1053 final EditText editText = new EditText(activity); 1054 editorRef.set(editText); 1055 editText.setHint("focused editText"); 1056 editText.setPrivateImeOptions(marker); 1057 editText.requestFocus(); 1058 layout.addView(editText); 1059 return layout; 1060 }, TestActivity.class); 1061 // Show IME. 1062 runOnMainSync(() -> editorRef.get().getContext().getSystemService( 1063 InputMethodManager.class).showSoftInput(editorRef.get(), 0)); 1064 1065 expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 1066 expectImeVisible(TIMEOUT); 1067 1068 // Create then show a Dialog. 1069 try (AutoCloseableWrapper<Dialog> dialogWrapper = 1070 createDialogWrapper(editorRef.get())) { 1071 instrumentation.waitForIdleSync(); 1072 // Verify IME became invisible when the Dialog is shown. 1073 // Note: the IME is partially visible behind dimmed layer and it won't match 1074 // the screenshot. 1075 expectImeInvisible(NOT_EXPECT_TIMEOUT); 1076 1077 runOnMainSync(() -> dialogWrapper.get().dismiss()); 1078 // Verify IME became visible when the Dialog has dismissed. 1079 expectImeVisible(TIMEOUT); 1080 } 1081 } 1082 } 1083 1084 /** 1085 * Test case for Bug 228766370. 1086 * 1087 * <p>This test ensures that IME will visible on an ime-focusable overlay window when another 1088 * activity behind the overlay that requests to show IME. <p/> 1089 */ 1090 @Test testImeVisibleOnImeFocusableOverlay()1091 public void testImeVisibleOnImeFocusableOverlay() throws Exception { 1092 try (MockImeSession imeSession = MockImeSession.create( 1093 mInstrumentation.getContext(), 1094 mInstrumentation.getUiAutomation(), 1095 new ImeSettings.Builder() 1096 .setInputViewHeight(NEW_KEYBOARD_HEIGHT) 1097 .setDrawsBehindNavBar(true))) { 1098 final ImeEventStream stream = imeSession.openEventStream(); 1099 TestActivity testActivity = TestActivity.startSync(activity -> { 1100 final View view = new View(activity); 1101 view.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); 1102 return view; 1103 }); 1104 1105 // Show an overlay with "IME Focusable" (NOT_FOCUSABLE | ALT_FOCUSABLE_IM) flags. 1106 runOnMainSync(() -> SystemUtil.runWithShellPermissionIdentity(() -> 1107 testActivity.showOverlayWindow(true /* imeFocusable */))); 1108 mInstrumentation.waitForIdleSync(); 1109 1110 // Start a next activity to expect IME should visible on top of the overlay. 1111 final String marker = getTestMarker(); 1112 final AtomicReference<EditText> editorRef = new AtomicReference<>(); 1113 new TestActivity.Starter().asNewTask().startSync(activity -> { 1114 final LinearLayout layout = new LinearLayout(activity); 1115 layout.setOrientation(LinearLayout.VERTICAL); 1116 layout.setGravity(Gravity.BOTTOM); 1117 final EditText editText = new EditText(activity); 1118 editorRef.set(editText); 1119 editText.setHint("focused editText"); 1120 editText.setPrivateImeOptions(marker); 1121 editText.requestFocus(); 1122 layout.addView(editText); 1123 return layout; 1124 }, TestActivity.class); 1125 // Show IME. 1126 runOnMainSync(() -> editorRef.get().getContext().getSystemService( 1127 InputMethodManager.class).showSoftInput(editorRef.get(), 0)); 1128 1129 expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 1130 expectImeVisible(TIMEOUT); 1131 } 1132 } 1133 1134 private enum TestSoftInputMode { 1135 UNCHANGED_WITH_BACKWARD_NAV, 1136 ALWAYS_HIDDEN_WITH_BACKWARD_NAV, 1137 HIDDEN_WITH_FORWARD_NAV 1138 } 1139 runRestoreImeVisibility(TestSoftInputMode mode, boolean expectImeVisible)1140 private void runRestoreImeVisibility(TestSoftInputMode mode, boolean expectImeVisible) 1141 throws Exception { 1142 final Instrumentation instrumentation = mInstrumentation; 1143 final WindowManager wm = instrumentation.getContext().getSystemService(WindowManager.class); 1144 // As restoring IME visibility behavior is only available when TaskSnapshot mechanism 1145 // enabled, skip the test when TaskSnapshot is not supported. 1146 assumeTrue("Restoring IME visibility not available when TaskSnapshot unsupported", 1147 wm.isTaskSnapshotSupported()); 1148 1149 try (MockImeSession imeSession = MockImeSession.create( 1150 instrumentation.getContext(), instrumentation.getUiAutomation(), 1151 new ImeSettings.Builder())) { 1152 final ImeEventStream stream = imeSession.openEventStream(); 1153 final String markerForActivity1 = getTestMarker(FIRST_EDIT_TEXT_TAG); 1154 final AtomicReference<EditText> editTextRef = new AtomicReference<>(); 1155 // Launch a test activity with focusing editText to show keyboard 1156 new TestActivity.Starter().withWindowingMode( 1157 WINDOWING_MODE_FULLSCREEN).startSync(activity -> { 1158 final LinearLayout layout = new LinearLayout(activity); 1159 final EditText editText = new EditText(activity); 1160 editTextRef.set(editText); 1161 editText.setHint("focused editText"); 1162 editText.setPrivateImeOptions(markerForActivity1); 1163 editText.requestFocus(); 1164 layout.addView(editText); 1165 activity.getWindow().getDecorView().getWindowInsetsController().show(ime()); 1166 if (mode == TestSoftInputMode.ALWAYS_HIDDEN_WITH_BACKWARD_NAV) { 1167 activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_HIDDEN); 1168 } 1169 return layout; 1170 }, TestActivity.class); 1171 1172 expectEvent(stream, editorMatcher("onStartInput", markerForActivity1), TIMEOUT); 1173 expectEvent(stream, editorMatcher("onStartInputView", markerForActivity1), TIMEOUT); 1174 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", 1175 View.VISIBLE, TIMEOUT); 1176 expectImeVisible(TIMEOUT); 1177 1178 // Launch another app task activity to hide keyboard 1179 new TestActivity.Starter().asNewTask().withWindowingMode( 1180 WINDOWING_MODE_FULLSCREEN).startSync(activity -> { 1181 activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_HIDDEN); 1182 return new LinearLayout(activity); 1183 }, TestActivity.class); 1184 expectEvent(stream, hideSoftInputMatcher(), TIMEOUT); 1185 expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT); 1186 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", 1187 View.GONE, TIMEOUT); 1188 expectImeInvisible(TIMEOUT); 1189 1190 if (mode == TestSoftInputMode.HIDDEN_WITH_FORWARD_NAV) { 1191 // Start new TestActivity on the same task with STATE_HIDDEN softInputMode. 1192 final String markerForActivity2 = getTestMarker(SECOND_EDIT_TEXT_TAG); 1193 new TestActivity.Starter().asSameTaskAndClearTop().withWindowingMode( 1194 WINDOWING_MODE_FULLSCREEN).startSync(activity -> { 1195 final LinearLayout layout = new LinearLayout(activity); 1196 final EditText editText = new EditText(activity); 1197 editText.setHint("focused editText"); 1198 editText.setPrivateImeOptions(markerForActivity2); 1199 editText.requestFocus(); 1200 layout.addView(editText); 1201 activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_HIDDEN); 1202 return layout; 1203 }, TestActivity.class); 1204 expectEvent(stream, editorMatcher("onStartInput", markerForActivity2), TIMEOUT); 1205 } else { 1206 // Press back key to back to the first test activity 1207 instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK); 1208 expectEvent(stream, editorMatcher("onStartInput", markerForActivity1), TIMEOUT); 1209 } 1210 1211 // Expect the IME visibility according to expectImeVisible 1212 // The expected result could be: 1213 // 1) The system can restore the IME visibility to show IME up when navigated back to 1214 // the original app task, even the IME is hidden when switching to the next task. 1215 // 2) The system won't restore the IME visibility in some softInputMode cases. 1216 if (expectImeVisible) { 1217 expectImeVisible(TIMEOUT); 1218 } else { 1219 expectImeInvisible(TIMEOUT); 1220 } 1221 } 1222 } 1223 runImeVisibilityWhenImeTransitionBetweenActivities(boolean instant)1224 private void runImeVisibilityWhenImeTransitionBetweenActivities(boolean instant) 1225 throws Exception { 1226 try (MockImeSession imeSession = MockImeSession.create( 1227 mInstrumentation.getContext(), 1228 mInstrumentation.getUiAutomation(), 1229 new ImeSettings.Builder() 1230 .setInputViewHeight(NEW_KEYBOARD_HEIGHT) 1231 .setDrawsBehindNavBar(true))) { 1232 final ImeEventStream stream = imeSession.openEventStream(); 1233 final String marker = getTestMarker(); 1234 1235 AtomicReference<EditText> editTextRef = new AtomicReference<>(); 1236 // Launch test activity with focusing editor 1237 final TestActivity testActivity = 1238 new TestActivity.Starter().withWindowingMode( 1239 WINDOWING_MODE_FULLSCREEN).startSync(activity -> { 1240 final LinearLayout layout = new LinearLayout(activity); 1241 layout.setOrientation(LinearLayout.VERTICAL); 1242 layout.setGravity(Gravity.BOTTOM); 1243 final EditText editText = new EditText(activity); 1244 editTextRef.set(editText); 1245 editText.setHint("focused editText"); 1246 editText.setPrivateImeOptions(marker); 1247 editText.requestFocus(); 1248 layout.addView(editText); 1249 final View decorView = activity.getWindow().getDecorView(); 1250 decorView.setFitsSystemWindows(true); 1251 decorView.getWindowInsetsController().show(ime()); 1252 return layout; 1253 }, TestActivity.class); 1254 expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT); 1255 expectEvent(stream, eventMatcher("showSoftInput"), TIMEOUT); 1256 expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 1257 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", 1258 View.VISIBLE, TIMEOUT); 1259 expectImeVisible(TIMEOUT); 1260 1261 WindowInsets initialRootWindowInsets = 1262 testActivity.getWindow().getDecorView().getRootWindowInsets(); 1263 1264 // Launch another test activity from another process with popup dialog. 1265 MockTestActivityUtil.launchSync(instant, TIMEOUT, 1266 Map.of(MockTestActivityUtil.EXTRA_KEY_SHOW_DIALOG, "true")); 1267 BySelector dialogSelector = By.clazz(AlertDialog.class).depth(0); 1268 UiDevice uiDevice = UiDevice.getInstance(mInstrumentation); 1269 assertNotNull(uiDevice.wait(Until.hasObject(dialogSelector), TIMEOUT)); 1270 1271 // Dismiss dialog and back to original test activity 1272 MockTestActivityUtil.sendBroadcastAction(MockTestActivityUtil.EXTRA_DISMISS_DIALOG, 1273 mUserHelper.getUserId()); 1274 1275 // Verify keyboard visibility should aligned with IME insets visibility. 1276 TestUtils.waitOnMainUntil( 1277 () -> testActivity.getWindow().getDecorView().getVisibility() == VISIBLE 1278 && testActivity.getWindow().getDecorView().hasWindowFocus(), TIMEOUT); 1279 // Wait for layout being stable in case insets visibility might not align with the 1280 // input view visibility. 1281 waitForInputViewLayoutStable(stream, LAYOUT_STABLE_THRESHOLD); 1282 1283 if (initialRootWindowInsets.isVisible(WindowInsets.Type.ime())) { 1284 expectImeVisible(TIMEOUT); 1285 } else { 1286 expectImeInvisible(TIMEOUT); 1287 } 1288 assertEquals(initialRootWindowInsets.getInsets(WindowInsets.Type.ime()), 1289 testActivity.getWindow().getDecorView().getRootWindowInsets().getInsets( 1290 WindowInsets.Type.ime())); 1291 } 1292 } 1293 runImeVisibilityTestWhenForceStopPackage(boolean instant)1294 private void runImeVisibilityTestWhenForceStopPackage(boolean instant) throws Exception { 1295 try (MockImeSession imeSession = MockImeSession.create( 1296 mInstrumentation.getContext(), 1297 mInstrumentation.getUiAutomation(), 1298 new ImeSettings.Builder())) { 1299 final ImeEventStream stream = imeSession.openEventStream(); 1300 final String marker = getTestMarker(); 1301 1302 // Make sure that MockIme isn't shown in the initial state. 1303 final ImeLayoutInfo lastLayout = 1304 waitForInputViewLayoutStable(stream, LAYOUT_STABLE_THRESHOLD); 1305 assertNull(lastLayout); 1306 expectImeInvisible(TIMEOUT); 1307 // Flush all the events happened before launching the test Activity. 1308 stream.skipAll(); 1309 1310 // Launch test activity with focusing an editor from remote process and expect the 1311 // IME is visible. 1312 try (AutoCloseable closable = MockTestActivityUtil.launchSync( 1313 instant, TIMEOUT, 1314 Map.of(MockTestActivityUtil.EXTRA_KEY_PRIVATE_IME_OPTIONS, marker))) { 1315 expectEvent(stream, editorMatcher("onStartInput", marker), START_INPUT_TIMEOUT); 1316 expectImeInvisible(TIMEOUT); 1317 1318 // Request showSoftInput, expect the request is valid and soft-keyboard visible. 1319 MockTestActivityUtil.sendBroadcastAction( 1320 MockTestActivityUtil.EXTRA_SHOW_SOFT_INPUT, mUserHelper.getUserId()); 1321 expectEvent(stream, eventMatcher("showSoftInput"), TIMEOUT); 1322 expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 1323 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", 1324 View.VISIBLE, TIMEOUT); 1325 expectImeVisible(TIMEOUT); 1326 1327 // Force stop test app package, and then expect IME should be invisible after the 1328 // remote process stopped by forceStopPackage. 1329 MockTestActivityUtil.forceStopPackage(); 1330 expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT); 1331 expectImeInvisible(TIMEOUT); 1332 } 1333 } 1334 } 1335 1336 /** 1337 * Test case for Bug 254624767. 1338 * 1339 * <p>This test ensures that when the app requests to show/hide IME according to the IME insets 1340 * visibility with {@link WindowInsets#isVisible(int)} during switching apps, verify the system 1341 * dispatches the IME insets visibility to the app correctly during that time. </p> 1342 */ 1343 @Test testImeInsetsInvisibleAfterBackingFromImeHiddenActivity()1344 public void testImeInsetsInvisibleAfterBackingFromImeHiddenActivity() throws Exception { 1345 try (MockImeSession imeSession = MockImeSession.create( 1346 mInstrumentation.getContext(), 1347 mInstrumentation.getUiAutomation(), 1348 new ImeSettings.Builder())) { 1349 final ImeEventStream stream = imeSession.openEventStream(); 1350 final String marker = getTestMarker(FOCUSED_EDIT_TEXT_TAG); 1351 1352 // Launch the first activity 1353 final EditText editText = launchTestActivity(marker); 1354 AtomicReference<CountDownLatch> imeInsetsHiddenLatchRef = new AtomicReference<>(); 1355 expectEvent(stream, editorMatcher("onStartInput", marker), START_INPUT_TIMEOUT); 1356 notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 1357 expectImeInvisible(TIMEOUT); 1358 1359 TestUtils.runOnMainSync(() -> { 1360 // Show IME with WindowInsets API and register onApplyWindowInsetsListener 1361 editText.getRootView().setOnApplyWindowInsetsListener( 1362 (v, insets) -> { 1363 if (!insets.isVisible(WindowInsets.Type.ime())) { 1364 if (imeInsetsHiddenLatchRef.get() != null) { 1365 imeInsetsHiddenLatchRef.get().countDown(); 1366 } 1367 } 1368 return v.onApplyWindowInsets(insets); 1369 }); 1370 editText.getViewTreeObserver().addOnWindowFocusChangeListener(hasFocus -> { 1371 // Test scenario: emulate the issue app implements to show IME when 1372 // focusing back if the last requested IME insets visibility is true. 1373 // And hides IME with clearing the editor focus when the app focus-out. 1374 if (hasFocus) { 1375 final boolean hasImeShown = 1376 editText.getRootWindowInsets().isVisible(ime()); 1377 if (hasImeShown) { 1378 editText.getWindowInsetsController().show(ime()); 1379 } 1380 } else { 1381 editText.getWindowInsetsController().hide(ime()); 1382 editText.clearFocus(); 1383 } 1384 }); 1385 editText.getWindowInsetsController().show(ime()); 1386 } 1387 ); 1388 expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 1389 expectImeVisible(TIMEOUT); 1390 1391 // Launch the second test activity with expecting to hide the IME. 1392 final TestActivity secondActivity = new TestActivity.Starter() 1393 .asNewTask().startSync(activity -> { 1394 activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_HIDDEN); 1395 return new LinearLayout(activity); 1396 }, TestActivity.class); 1397 expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT); 1398 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", 1399 View.GONE, TIMEOUT); 1400 expectImeInvisible(TIMEOUT); 1401 1402 // Back to first activity 1403 imeInsetsHiddenLatchRef.set(new CountDownLatch(1)); 1404 runOnMainSync(secondActivity::onBackPressed); 1405 1406 // Verify the visibility of IME insets should be hidden in onApplyWindowInsets and 1407 // expect the first activity hides the IME according to the received IME insets 1408 // visibility when backing from the second activity. 1409 imeInsetsHiddenLatchRef.get().await(5, TimeUnit.SECONDS); 1410 notExpectEvent(stream, editorMatcher("onStartInputView", marker), NOT_EXPECT_TIMEOUT); 1411 expectImeInvisible(TIMEOUT); 1412 } 1413 } 1414 1415 /** 1416 * Test case for Bug 256739702. 1417 * 1418 * <p>This test ensures that when "android:screenSize|Orientation" is not set and the keyboard 1419 * is shown implicitly in fullscreen mode, it will not disappear after the user rotates screen. 1420 */ 1421 @Test testRotateScreenWithKeyboardShownImplicitly()1422 public void testRotateScreenWithKeyboardShownImplicitly() throws Exception { 1423 // Test only when both portrait and landscape mode are supported. 1424 final PackageManager pm = mInstrumentation.getTargetContext().getPackageManager(); 1425 assumeTrue(pm.hasSystemFeature(PackageManager.FEATURE_SCREEN_PORTRAIT)); 1426 assumeTrue(pm.hasSystemFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE)); 1427 final boolean isFixedToUserRotation = 1428 "enabled".equals(SystemUtil.runShellCommand(FIXED_TO_USER_ROTATION_CMD).trim()); 1429 assumeFalse("Device shouldn't have fixed rotation.", isFixedToUserRotation); 1430 1431 final InputMethodManager imm = mInstrumentation 1432 .getTargetContext().getSystemService(InputMethodManager.class); 1433 // Disable auto-rotate screen and set the screen orientation to portrait mode. 1434 setAutoRotateScreen(false); 1435 final UiDevice uiDevice = UiDevice.getInstance(mInstrumentation); 1436 uiDevice.setOrientationPortrait(); 1437 mInstrumentation.waitForIdleSync(); 1438 1439 // Set FullscreenModePolicy as OS_DEFAULT to call the original 1440 // InputMethodService#onEvaluateFullscreenMode() 1441 try (MockImeSession imeSession = MockImeSession.create( 1442 mInstrumentation.getContext(), 1443 mInstrumentation.getUiAutomation(), 1444 new ImeSettings.Builder().setFullscreenModePolicy( 1445 ImeSettings.FullscreenModePolicy.OS_DEFAULT))) { 1446 final ImeEventStream stream = imeSession.openEventStream(); 1447 1448 final String marker = getTestMarker(); 1449 // TestActivity2 is identical to TestActivity but doesn't handle any configChanges. 1450 final EditText editText = launchTestActivity2(marker); 1451 1452 expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT); 1453 notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 1454 expectImeInvisible(TIMEOUT); 1455 assertTrue("hasActiveInputConnection() must return true if the View has IME focus", 1456 getOnMainSync(() -> imm.hasActiveInputConnection(editText))); 1457 assumeFalse("onEvaluateFullscreenMode() should be false for portrait", 1458 expectCommand( 1459 stream, imeSession.callGetOnEvaluateFullscreenMode(), TIMEOUT) 1460 .getReturnBooleanValue()); 1461 1462 // Call ShowSoftInput() implicitly 1463 assertTrue("showSoftInput must success if the View has IME focus", 1464 getOnMainSync(() -> imm.showSoftInput(editText, 1465 InputMethodManager.SHOW_IMPLICIT))); 1466 1467 expectEvent(stream, showSoftInputMatcher(0), TIMEOUT); 1468 expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 1469 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", 1470 View.VISIBLE, TIMEOUT); 1471 expectImeVisible(TIMEOUT); 1472 1473 // Rotate screen to landscape. 1474 uiDevice.setOrientationLandscape(); 1475 mInstrumentation.waitForIdleSync(); 1476 expectImeVisible(TIMEOUT); 1477 assertTrue("IME should be in fullscreen mode", 1478 getOnMainSync(() -> imm.isFullscreenMode())); 1479 } finally { 1480 setAutoRotateScreen(true); 1481 uiDevice.setOrientationNatural(); 1482 } 1483 } 1484 1485 /** 1486 * Test case for Bug 222064495. 1487 * 1488 * <p>Test that the IME should be hidden when the IME layering target overlay with 1489 * "NOT_FOCUSABLE | ALT_FOCUSABLE_IM" flags popup during pressing the recents key to the 1490 * overview screen.</b> 1491 */ 1492 @RequireNotVisibleBackgroundUsers(reason = 1493 "To pass on secondary user on secondary display, this test should use UinputKeyboard " 1494 + "instead of `mInstrumentation.sendKeyDownUpSync`. b/368974455 will allow " 1495 + "UinputKeyboard to send KeyEvents to specific displays.") 1496 @Test testImeHiddenWhenImeLayeringTargetDelayedToShowInAppSwitch()1497 public void testImeHiddenWhenImeLayeringTargetDelayedToShowInAppSwitch() throws Exception { 1498 assumeTrue(hasRecentsScreen()); 1499 1500 try (MockImeSession imeSession = MockImeSession.create( 1501 mInstrumentation.getContext(), 1502 mInstrumentation.getUiAutomation(), 1503 new ImeSettings.Builder())) { 1504 final ImeEventStream stream = imeSession.openEventStream(); 1505 final String marker = getTestMarker(); 1506 1507 final TestActivity testActivity = TestActivity.startSync(activity -> { 1508 final LinearLayout layout = new LinearLayout(activity); 1509 layout.setOrientation(LinearLayout.VERTICAL); 1510 1511 final EditText editText = new EditText(activity); 1512 layout.addView(editText); 1513 editText.setHint("focused editText"); 1514 editText.setPrivateImeOptions(marker); 1515 editText.requestFocus(); 1516 activity.getWindow().getDecorView().getWindowInsetsController().show(ime()); 1517 return layout; 1518 }); 1519 1520 expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 1521 expectImeVisible(TIMEOUT); 1522 1523 // Intentionally showing an overlay with "IME Focusable" (NOT_FOCUSABLE | 1524 // ALT_FOCUSABLE_IM) flags during pressing the recents key to the overview screen. 1525 testActivity.getWindow().getDecorView().postDelayed( 1526 () -> SystemUtil.runWithShellPermissionIdentity(() -> 1527 testActivity.showOverlayWindow(true /* imeFocusable */)), 100); 1528 // TODO(b/368974455): Use UinputKeyboard instead. 1529 mInstrumentation.sendKeyDownUpSync( 1530 KeyEvent.KEYCODE_RECENT_APPS); 1531 1532 // Expect the IME should hidden by the IME not attachable on the activity when the 1533 // overlay popup. 1534 expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT); 1535 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", 1536 View.GONE, TIMEOUT); 1537 expectImeInvisible(TIMEOUT); 1538 } finally { 1539 // Back to home to clean up states after the test finished. 1540 // TODO(b/368974455): Use UinputKeyboard instead. 1541 UiDevice.getInstance(mInstrumentation).pressHome(); 1542 } 1543 } 1544 1545 /** 1546 * Test the IME visibility when in split-screen mode, switching the focus to the app task with 1547 * {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_HIDDEN} flag from the app showing the 1548 * IME will expect to be hidden. 1549 */ 1550 @Test testImeHiddenWhenFocusToAppWithStateHiddenFlagInMultiWindowMode()1551 public void testImeHiddenWhenFocusToAppWithStateHiddenFlagInMultiWindowMode() throws Exception { 1552 assumeTrue(TestUtils.supportsSplitScreenMultiWindow()); 1553 1554 try (MockImeSession imeSession = MockImeSession.create( 1555 mInstrumentation.getContext(), 1556 mInstrumentation.getUiAutomation(), 1557 new ImeSettings.Builder())) { 1558 final ImeEventStream stream = imeSession.openEventStream(); 1559 final String marker = getTestMarker(); 1560 1561 // Launch an editor activity to be on the split primary task. 1562 final AtomicReference<EditText> editTextRef = new AtomicReference<>(); 1563 final TestActivity splitPrimaryActivity = TestActivity.startSync(activity -> { 1564 final LinearLayout layout = new LinearLayout(activity); 1565 layout.setOrientation(LinearLayout.VERTICAL); 1566 final EditText editText = new EditText(activity); 1567 editTextRef.set(editText); 1568 layout.addView(editText); 1569 editText.setHint("focused editText"); 1570 editText.setPrivateImeOptions(marker); 1571 editText.requestFocus(); 1572 return layout; 1573 }); 1574 expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT); 1575 notExpectEvent(stream, editorMatcher("onStartInputView", marker), NOT_EXPECT_TIMEOUT); 1576 expectImeInvisible(TIMEOUT); 1577 1578 // Launch another activity with SOFT_INPUT_STATE_HIDDEN flag to be on the split 1579 // secondary task, expect the IME won't receive onStartInputView and invisible. 1580 final TestActivity splitSecondaryActivity = new TestActivity.Starter() 1581 .asMultipleTask() 1582 .withAdditionalFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT) 1583 .startSync(splitPrimaryActivity, activity -> { 1584 activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_HIDDEN); 1585 return new LinearLayout(activity); 1586 }, TestActivity2.class); 1587 notExpectEvent(stream, eventMatcher("onStartInputView"), 1588 NOT_EXPECT_TIMEOUT); 1589 expectImeInvisible(TIMEOUT); 1590 1591 final var editText = editTextRef.get(); 1592 final var display = editText.getContext().getDisplay(); 1593 /* 1594 * Since this test relies on window focus with multiple windows involved, we need to 1595 * use a global method of emulating touch that goes through the entire pipeline. This 1596 * ensures that the window manager is aware of the tap that occurred, and provides 1597 * window focus to the tapped window. 1598 */ 1599 try (var touch = new UinputTouchScreen(mInstrumentation, display)) { 1600 // Tap the editor on the split primary task to focus the window and show the IME. 1601 touch.tapOnViewCenter(editText); 1602 // TODO(b/280797309): The first tap sends the IME show request before the 1603 // input focus changes, so we have to wait for that and tap again. 1604 TestUtils.waitOnMainUntil(editText::hasWindowFocus, TIMEOUT); 1605 touch.tapOnViewCenter(editText); 1606 1607 expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 1608 expectImeVisible(TIMEOUT); 1609 1610 // Tap the split secondary task to switch focus and expect the IME will be hidden. 1611 touch.tapOnViewCenter(splitSecondaryActivity.getWindow().getDecorView()); 1612 } 1613 expectEvent(stream, hideSoftInputMatcher(), TIMEOUT); 1614 expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT); 1615 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", 1616 View.GONE, TIMEOUT); 1617 expectImeInvisible(TIMEOUT); 1618 } 1619 } 1620 1621 /** 1622 * Test case for Bug 226689544. 1623 * 1624 * Test to verify that the IME is visible, after the following actions: 1625 * 1. Open primary activity. 1626 * 2. Open second activity from the first activity in split screen. 1627 * 3. Open and show a dialog with editText in the second activity. 1628 * 4. Focus/click on first activity, then click on the editText. 1629 */ 1630 @Test 1631 @FlakyTest testIMEVisibleInSplitScreenAfterGainingFocus()1632 public void testIMEVisibleInSplitScreenAfterGainingFocus() throws Exception { 1633 assumeTrue(TestUtils.supportsSplitScreenMultiWindow()); 1634 1635 try (MockImeSession imeSession = MockImeSession.create( 1636 mInstrumentation.getContext(), 1637 mInstrumentation.getUiAutomation(), 1638 new ImeSettings.Builder())) { 1639 final ImeEventStream stream = imeSession.openEventStream(); 1640 final String marker = getTestMarker(); 1641 1642 final TestActivity splitPrimaryActivity = TestActivity.startSync(LinearLayout::new); 1643 1644 final AtomicReference<AlertDialog> dialogRef = new AtomicReference<>(); 1645 final AtomicReference<EditText> editTextRef = new AtomicReference<>(); 1646 try { 1647 // Launch another activity with SOFT_INPUT_STATE_UNCHANGED flag to be on the split 1648 // secondary task as well as on its dialog, so that the IME is not visible by 1649 // default on large screens, when showing the dialog. 1650 new TestActivity.Starter() 1651 .asMultipleTask() 1652 .withAdditionalFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT) 1653 .startSync(splitPrimaryActivity, activity -> { 1654 activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_UNCHANGED); 1655 1656 final EditText editText = new EditText(activity); 1657 editText.setHint("focused editText"); 1658 editText.setPrivateImeOptions(marker); 1659 editText.requestFocus(); 1660 final AlertDialog dialog = new AlertDialog.Builder(activity) 1661 .setTitle("DialogWithEditText") 1662 .setCancelable(false) 1663 .setView(editText) 1664 .create(); 1665 dialog.getWindow().setSoftInputMode(SOFT_INPUT_STATE_UNCHANGED); 1666 dialog.show(); 1667 dialogRef.set(dialog); 1668 editTextRef.set(editText); 1669 return new LinearLayout(activity); 1670 }, TestActivity2.class); 1671 1672 View decor = splitPrimaryActivity.getWindow().getDecorView(); 1673 CountDownLatch latch = new CountDownLatch(1); 1674 ViewTreeObserver observer = decor.getViewTreeObserver(); 1675 observer.addOnDrawListener(() -> { 1676 if (splitPrimaryActivity.isInMultiWindowMode()) { 1677 // check activity in multi-window mode after relayoutWindow. 1678 latch.countDown(); 1679 } 1680 }); 1681 1682 latch.await(LAYOUT_STABLE_THRESHOLD, TimeUnit.MILLISECONDS); 1683 1684 final var editText = editTextRef.get(); 1685 final var display = editText.getContext().getDisplay(); 1686 /* 1687 * Since this test relies on window focus with multiple windows involved, we need 1688 * to use a global method of emulating touch that goes through the entire pipeline. 1689 * This ensures that the window manager is aware of the tap that occurred, and 1690 * provides window focus to the tapped window. 1691 */ 1692 try (var touch = new UinputTouchScreen(mInstrumentation, display)) { 1693 // Tap on the first activity to change focus 1694 touch.tapOnViewCenter(splitPrimaryActivity.getWindow().getDecorView()); 1695 1696 notExpectEvent(stream, eventMatcher("onStartInputView"), 1697 NOT_EXPECT_TIMEOUT); 1698 expectImeInvisible(TIMEOUT); 1699 1700 // Tap on the edit text in the split dialog to show the IME. 1701 touch.tapOnViewCenter(editText); 1702 // TODO(b/280797309): The first tap sends the IME show request before the 1703 // input focus changes, so we have to wait for that and tap again. 1704 TestUtils.waitOnMainUntil(editText::hasWindowFocus, TIMEOUT); 1705 touch.tapOnViewCenter(editText); 1706 // wait on event to make sure touch event is injected. 1707 expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 1708 expectImeVisible(TIMEOUT); 1709 } 1710 } finally { 1711 // dismiss dialog, in case it wasn't closed properly 1712 if (dialogRef.get() != null) { 1713 dialogRef.get().dismiss(); 1714 } 1715 } 1716 } 1717 } 1718 1719 /** 1720 * A regression Test for Bug 283342812 1721 * 1722 * 1. Open primary activity. 1723 * 2. Open second activity with 2 editText views from the first activity in split screen. 1724 * 3. Focus the 1st editor and invoke {@link WindowInsetsController#show} to make IME visible. 1725 * 4. Press the back key to make IME invisible. 1726 * 5. Focus the 2nd editor and invoke {@link WindowInsetsController#show} to make IME visible. 1727 * 6. Finish the primary activity to exit split screen mode. 1728 * 7. Test step 3-5 again to ensure it passes after exiting split screen mode. 1729 */ 1730 @Test 1731 @FlakyTest testIMEVisibleInSplitScreenWithWindowInsetsApi()1732 public void testIMEVisibleInSplitScreenWithWindowInsetsApi() throws Throwable { 1733 assumeTrue(TestUtils.supportsSplitScreenMultiWindow()); 1734 1735 try (MockImeSession imeSession = MockImeSession.create( 1736 mInstrumentation.getContext(), 1737 mInstrumentation.getUiAutomation(), 1738 new ImeSettings.Builder())) { 1739 final ImeEventStream stream = imeSession.openEventStream(); 1740 final TestActivity splitPrimaryActivity = TestActivity.startSync(LinearLayout::new); 1741 1742 // Launch another test activity in split-screen with 2 editor views 1743 final AtomicReference<EditText> editText1Ref = new AtomicReference<>(); 1744 final AtomicReference<EditText> editText2Ref = new AtomicReference<>(); 1745 final String editText1Marker = getTestMarker(FIRST_EDIT_TEXT_TAG); 1746 final String editText2Marker = getTestMarker(SECOND_EDIT_TEXT_TAG); 1747 final TestActivity testActivity2 = new TestActivity.Starter() 1748 .asMultipleTask() 1749 .withAdditionalFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT) 1750 .startSync(splitPrimaryActivity, activity -> { 1751 LinearLayout layout = new LinearLayout(activity); 1752 activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_HIDDEN); 1753 1754 final EditText editText1 = new EditText(activity); 1755 editText1.setHint("This is editText1"); 1756 editText1.setPrivateImeOptions(editText1Marker); 1757 editText1Ref.set(editText1); 1758 1759 final EditText editText2 = new EditText(activity); 1760 editText2.setHint("This is editText2"); 1761 editText2.setPrivateImeOptions(editText2Marker); 1762 editText2Ref.set(editText2); 1763 1764 layout.addView(editText1); 1765 layout.addView(editText2); 1766 return layout; 1767 }, TestActivity2.class); 1768 1769 notExpectEvent(stream, eventMatcher("onStartInputView"), 1770 NOT_EXPECT_TIMEOUT); 1771 expectImeInvisible(TIMEOUT); 1772 1773 final var display = editText1Ref.get().getContext().getDisplay(); 1774 /* 1775 * Since this test relies on window focus with multiple windows involved, we need to 1776 * use a global method of emulating touch that goes through the entire pipeline. This 1777 * ensures that the window manager is aware of the tap that occurred, and provides 1778 * window focus to the tapped window. 1779 */ 1780 try (var touch = new UinputTouchScreen(mInstrumentation, display)) { 1781 // Tap on the test activity to change focus 1782 touch.tapOnViewCenter(testActivity2.getWindow().getDecorView()); 1783 1784 ThrowingRunnable testProcedureForTestActivity2 = () -> { 1785 // Focus the 1st editor and show the IME with WindowInsets API. 1786 testActivity2.runOnUiThread(() -> { 1787 editText1Ref.get().requestFocus(); 1788 editText1Ref.get() 1789 .getWindowInsetsController().show(WindowInsets.Type.ime()); 1790 }); 1791 expectEvent(stream, editorMatcher("onStartInputView", editText1Marker), 1792 TIMEOUT); 1793 expectImeVisible(TIMEOUT); 1794 1795 // Press the back key to make the IME invisible. 1796 mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK); 1797 expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT); 1798 expectImeInvisible(TIMEOUT); 1799 1800 assertTrue("TestActivity2 is still focused after IME is hidden", 1801 testActivity2.hasWindowFocus()); 1802 1803 // Focus the 2nd editor and show the IME with WindowInsets API. 1804 testActivity2.runOnUiThread(() -> { 1805 editText2Ref.get().requestFocus(); 1806 editText2Ref.get() 1807 .getWindowInsetsController().show(WindowInsets.Type.ime()); 1808 }); 1809 expectEvent(stream, editorMatcher("onStartInputView", editText2Marker), 1810 TIMEOUT); 1811 expectImeVisible(TIMEOUT); 1812 }; 1813 testProcedureForTestActivity2.run(); 1814 1815 // Finish the primary activity to exit split-screen mode. 1816 splitPrimaryActivity.runOnUiThread(splitPrimaryActivity::finish); 1817 TestUtils.waitOnMainUntil(() -> { 1818 final View decorView = testActivity2.getWindow().getDecorView(); 1819 return decorView.hasWindowFocus() && decorView.getVisibility() == VISIBLE; 1820 }, TIMEOUT, "Activity should visible & focused when exiting split-screen mode"); 1821 1822 // Rerun the test procedure to ensure it passes after exiting split-screen mode. 1823 testProcedureForTestActivity2.run(); 1824 } 1825 } 1826 } 1827 1828 /** 1829 * A regression Test for Bug 226033399. 1830 * 1831 * <p>This test verifies that the keyboard remains visible when a notification comes. 1832 * This test runs only when the screen is big enough. If the screen is small, it may make sense 1833 * to dismiss the keyboard while a notification is being displayed. We also skip this test on 1834 * non-phone, non-tablet form factors. 1835 */ 1836 // Instant apps cannot post notification. 1837 @AppModeFull 1838 @Test testIMEVisibleWhenNotificationComes()1839 public void testIMEVisibleWhenNotificationComes() throws Throwable { 1840 final Context targetContext = mInstrumentation.getTargetContext(); 1841 final PackageManager pm = targetContext.getPackageManager(); 1842 // Exclude major known non-phone, non-tablet form factors which have different notification 1843 // UIs. 1844 assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)); 1845 assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)); 1846 assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_WATCH)); 1847 final NotificationManager notificationManager = 1848 targetContext.getSystemService(NotificationManager.class); 1849 assumeNotNull(notificationManager); 1850 // Usually notification permission should be auto-granted by the test runner. 1851 // Skip the test if notification is disabled for some other reason. 1852 assumeTrue(notificationManager.areNotificationsEnabled()); 1853 1854 final InputMethodManager imm = targetContext.getSystemService(InputMethodManager.class); 1855 final int notificationId = 12345; 1856 try (MockImeSession imeSession = MockImeSession.create( 1857 mInstrumentation.getContext(), 1858 mInstrumentation.getUiAutomation(), 1859 new ImeSettings.Builder())) { 1860 final ImeEventStream stream = imeSession.openEventStream(); 1861 1862 final String marker = getTestMarker(); 1863 final EditText editText = launchTestActivity(marker); 1864 1865 // Skip the test if the screen size is small. 1866 final int smallestScreenWidthDp = 1867 editText.getContext().getResources().getConfiguration().smallestScreenWidthDp; 1868 Log.d(TAG, "smallestScreenWidthDp = " + smallestScreenWidthDp); 1869 assumeTrue(smallestScreenWidthDp >= 400); 1870 1871 // 1. Show keyboard. 1872 expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT); 1873 assertTrue("showSoftInput must success if the View has IME focus", 1874 getOnMainSync(() -> imm.showSoftInput(editText, 0))); 1875 expectImeVisible(TIMEOUT); 1876 1877 // 2. Post a notification and verify that the keyboard is still visible. 1878 final NotificationChannel channel = new NotificationChannel("test" /* id */, 1879 "Test Channel" /* name */, NotificationManager.IMPORTANCE_HIGH); 1880 notificationManager.createNotificationChannel(channel); 1881 final String notificationTitle = "notification-" + marker; 1882 notificationManager.notify( 1883 notificationId, 1884 new Notification.Builder(targetContext, channel.getId()) 1885 .setContentTitle(notificationTitle) 1886 .setContentText("testIMEVisibleWhenNotificationComes") 1887 .setSmallIcon(android.R.drawable.ic_info) 1888 .build()); 1889 UiDevice uiDevice = UiDevice.getInstance(mInstrumentation); 1890 // Wait until the notification is visible. If TIMEOUT has passed and the notification 1891 // is not visible, it's fine - the keyboard should remain visible in that case too. 1892 uiDevice.wait(Until.hasObject(By.text(notificationTitle)), TIMEOUT); 1893 expectImeVisible(TIMEOUT); 1894 1895 // 3. Dismiss the notification and verify that the keyboard is still visible. 1896 notificationManager.cancel(notificationId); 1897 uiDevice.wait(Until.gone(By.text(notificationTitle)), TIMEOUT); 1898 expectImeVisible(TIMEOUT); 1899 } finally { 1900 // Make sure to dismiss the notification even if the test failed. 1901 notificationManager.cancel(notificationId); 1902 } 1903 } 1904 1905 @Test 1906 @RequiresFlagsEnabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER) testQuickDoubleSwipeBackHidesImeAndSendsEventToApp_OnBackInvokedCallbackEnabled()1907 public void testQuickDoubleSwipeBackHidesImeAndSendsEventToApp_OnBackInvokedCallbackEnabled() 1908 throws Exception { 1909 testQuickDoubleSwipeBackHidesImeAndSendsEventToApp(true); 1910 } 1911 1912 @Test 1913 @RequiresFlagsEnabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER) testQuickDoubleSwipeBackHidesImeAndSendsEventToApp_OnBackInvokedCallbackDisabled()1914 public void testQuickDoubleSwipeBackHidesImeAndSendsEventToApp_OnBackInvokedCallbackDisabled() 1915 throws Exception { 1916 testQuickDoubleSwipeBackHidesImeAndSendsEventToApp(false); 1917 } 1918 1919 /** 1920 * A regression test for Bug 375986921. 1921 * 1922 * This test verifies that invoking a key back event twice quickly after each other will hide 1923 * the IME first and then go back to the previous activity. 1924 */ testQuickDoubleSwipeBackHidesImeAndSendsEventToApp( boolean onBackInvokedCallbackEnabled)1925 private void testQuickDoubleSwipeBackHidesImeAndSendsEventToApp( 1926 boolean onBackInvokedCallbackEnabled) throws Exception { 1927 try (MockImeSession imeSession = MockImeSession.create( 1928 mInstrumentation.getContext(), 1929 mInstrumentation.getUiAutomation(), 1930 new ImeSettings.Builder())) { 1931 final ImeEventStream stream = imeSession.openEventStream(); 1932 final String marker = getTestMarker(); 1933 1934 mInstrumentation.getTargetContext().getApplicationInfo().setEnableOnBackInvokedCallback( 1935 onBackInvokedCallbackEnabled); 1936 1937 // Launch an editor activity to be on the split primary task. 1938 final AtomicReference<EditText> editTextRef = new AtomicReference<>(); 1939 final TestActivity testActivity = TestActivity.startSync(activity -> { 1940 final LinearLayout layout = new LinearLayout(activity); 1941 layout.setOrientation(LinearLayout.VERTICAL); 1942 final EditText editText = new EditText(activity); 1943 editTextRef.set(editText); 1944 layout.addView(editText); 1945 editText.setHint("focused editText"); 1946 editText.setPrivateImeOptions(marker); 1947 editText.requestFocus(); 1948 return layout; 1949 }); 1950 expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT); 1951 1952 // Show IME and make sure it has window focus 1953 TestUtils.runOnMainSync(() -> { 1954 editTextRef.get().requestFocus(); 1955 editTextRef.get().getWindowInsetsController().show(WindowInsets.Type.ime()); 1956 }); 1957 expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT); 1958 expectImeVisible(TIMEOUT); 1959 assertTrue(testActivity.hasWindowFocus()); 1960 1961 // First back event should hide the IME, the second should be ignored by the IME and 1962 // close the activity. 1963 mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK); 1964 CtsWindowInfoUtils.waitForStableWindowGeometry( 1965 Duration.ofMillis(LAYOUT_STABLE_THRESHOLD)); 1966 mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK); 1967 mInstrumentation.waitForIdleSync(); 1968 1969 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", View.GONE, 1970 TIMEOUT); 1971 1972 // Make sure the activity was stopped by the second back key event. 1973 try { 1974 TestUtils.waitOnMainUntil(testActivity::isStopped, TIMEOUT); 1975 } catch (TimeoutException e) { 1976 throw new AssertionError("Activity should have been stopped", e); 1977 } 1978 } 1979 } 1980 1981 setAutoRotateScreen(boolean enable)1982 private void setAutoRotateScreen(boolean enable) { 1983 try { 1984 final Instrumentation instrumentation = mInstrumentation; 1985 SystemUtil.runShellCommand(instrumentation, enable ? ENABLE_AUTO_ROTATE_CMD : 1986 DISABLE_AUTO_ROTATE_CMD); 1987 instrumentation.waitForIdleSync(); 1988 } catch (IOException io) { 1989 fail("Couldn't enable/disable auto-rotate screen"); 1990 } 1991 } 1992 getFloatingImeSettings(@olorInt int navigationBarColor)1993 private static ImeSettings.Builder getFloatingImeSettings(@ColorInt int navigationBarColor) { 1994 final ImeSettings.Builder builder = new ImeSettings.Builder(); 1995 builder.setWindowFlags(0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 1996 // As documented, Window#setNavigationBarColor() is actually ignored when the IME window 1997 // does not have FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS. We are calling setNavigationBarColor() 1998 // to ensure it. 1999 builder.setNavigationBarColor(navigationBarColor); 2000 return builder; 2001 } 2002 2003 /** 2004 * Whether enabling a compatibility flag to clear {@link InputMethodManager#SHOW_FORCED} flag 2005 * for the given {@code packageName} of the app when it's leaving. 2006 * 2007 * @return {@code true} if the compatibility flag is enabled. 2008 */ isClearShowForcedFlagEnabled(String packageName)2009 private static boolean isClearShowForcedFlagEnabled(String packageName) { 2010 AtomicBoolean result = new AtomicBoolean(); 2011 runWithShellPermissionIdentity(() -> result.set( 2012 CompatChanges.isChangeEnabled(CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING, packageName, 2013 UserHandle.CURRENT))); 2014 return result.get(); 2015 } 2016 2017 /** Whether the IME DisplayArea is organized by WM Shell. */ isImeOrganized(int displayId)2018 private static boolean isImeOrganized(int displayId) { 2019 final WindowManagerState wmState = new WindowManagerState(); 2020 wmState.computeState(); 2021 WindowManagerState.DisplayArea imeContainer = wmState.getImeContainer(displayId); 2022 assertNotNull("ImeContainer not found for display id: " + displayId, imeContainer); 2023 return imeContainer.isOrganized(); 2024 } 2025 createDialogWrapper(@onNull EditText editor)2026 private static AutoCloseableWrapper<Dialog> createDialogWrapper(@NonNull EditText editor) { 2027 return AutoCloseableWrapper.create( 2028 TestUtils.getOnMainSync(() -> { 2029 final Dialog dialog = new Dialog(editor.getContext()); 2030 final TextView textView = new TextView(editor.getContext()); 2031 textView.setText("Dialog"); 2032 dialog.setContentView(textView); 2033 2034 // Dim the background. 2035 WindowManager.LayoutParams lp = new WindowManager.LayoutParams(); 2036 lp.copyFrom(dialog.getWindow().getAttributes()); 2037 lp.width = MATCH_PARENT; 2038 lp.height = MATCH_PARENT; 2039 lp.flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND; 2040 lp.dimAmount = 0.3f; 2041 dialog.getWindow().setAttributes(lp); 2042 dialog.show(); 2043 return dialog; 2044 }), dialog -> TestUtils.runOnMainSync(dialog::dismiss)); 2045 } 2046 2047 /** 2048 * Whether the device has supported the recents screen. 2049 */ hasRecentsScreen()2050 private boolean hasRecentsScreen() { 2051 try { 2052 Context context = mInstrumentation.getContext(); 2053 return context.getResources().getBoolean( 2054 Resources.getSystem().getIdentifier("config_hasRecents", "bool", "android")); 2055 } catch (Resources.NotFoundException e) { 2056 return false; 2057 } 2058 } 2059 } 2060