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