1 /* 2 * Copyright (C) 2023 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.server.wm.ime; 18 19 import static android.server.wm.InputMethodVisibilityVerifier.expectImeInvisible; 20 import static android.server.wm.InputMethodVisibilityVerifier.expectImeVisible; 21 import static android.server.wm.MockImeHelper.createManagedMockImeSession; 22 import static android.server.wm.UiDeviceUtils.pressBackButton; 23 import static android.server.wm.WindowManagerState.STATE_RESUMED; 24 import static android.view.Display.DEFAULT_DISPLAY; 25 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; 26 import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE; 27 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; 28 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED; 29 30 import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher; 31 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand; 32 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent; 33 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEventWithKeyValue; 34 import static com.android.cts.mockime.ImeEventStreamTestUtils.hideSoftInputMatcher; 35 import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent; 36 37 import static com.google.common.truth.Truth.assertThat; 38 import static com.google.common.truth.Truth.assertWithMessage; 39 40 import static org.junit.Assert.assertEquals; 41 import static org.junit.Assert.assertFalse; 42 import static org.junit.Assert.assertTrue; 43 import static org.junit.Assume.assumeFalse; 44 import static org.junit.Assume.assumeTrue; 45 46 import android.app.Activity; 47 import android.content.ComponentName; 48 import android.content.Context; 49 import android.content.ContextWrapper; 50 import android.content.Intent; 51 import android.content.res.Configuration; 52 import android.graphics.Rect; 53 import android.os.Bundle; 54 import android.os.SystemClock; 55 import android.platform.test.annotations.Presubmit; 56 import android.platform.test.annotations.RequiresFlagsDisabled; 57 import android.platform.test.flag.junit.CheckFlagsRule; 58 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 59 import android.server.wm.MultiDisplayTestBase; 60 import android.server.wm.WindowManagerState; 61 import android.server.wm.WindowManagerState.DisplayContent; 62 import android.server.wm.WindowManagerState.WindowState; 63 import android.server.wm.intent.Activities; 64 import android.text.TextUtils; 65 import android.view.View; 66 import android.view.Window; 67 import android.view.WindowManager; 68 import android.view.inputmethod.EditorInfo; 69 import android.view.inputmethod.InputConnection; 70 import android.view.inputmethod.InputMethodManager; 71 import android.widget.EditText; 72 import android.widget.LinearLayout; 73 74 import com.android.compatibility.common.util.PollingCheck; 75 import com.android.compatibility.common.util.SystemUtil; 76 import com.android.cts.mockime.ImeCommand; 77 import com.android.cts.mockime.ImeEventStream; 78 import com.android.cts.mockime.MockImeSession; 79 80 import org.junit.Before; 81 import org.junit.Rule; 82 import org.junit.Test; 83 84 import java.util.List; 85 import java.util.concurrent.TimeUnit; 86 87 /** 88 * Build/Install/Run: 89 * atest CtsWindowManagerDeviceIme:MultiDisplayImeTests 90 */ 91 @Presubmit 92 @android.server.wm.annotation.Group3 93 public class MultiDisplayImeTests extends MultiDisplayTestBase { 94 95 @Rule 96 public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); 97 98 static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(2); 99 static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5); 100 101 @Before 102 @Override setUp()103 public void setUp() throws Exception { 104 super.setUp(); 105 106 assumeTrue(supportsMultiDisplay()); 107 assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme()); 108 } 109 110 @Test testImeWindowCanSwitchToDifferentDisplays()111 public void testImeWindowCanSwitchToDifferentDisplays() throws Exception { 112 final MockImeSession mockImeSession = createManagedMockImeSession(this); 113 final TestActivitySession<ImeTestActivity> imeTestActivitySession = 114 createManagedTestActivitySession(); 115 final TestActivitySession<ImeTestActivity2> imeTestActivitySession2 = 116 createManagedTestActivitySession(); 117 118 // Create a virtual display and launch an activity on it. 119 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 120 .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL) 121 .setSimulateDisplay(true) 122 .createDisplay(); 123 124 final ImeEventStream stream = mockImeSession.openEventStream(); 125 126 imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class, 127 newDisplay.mId); 128 129 expectEvent(stream, editorMatcher("onStartInput", 130 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 131 132 // Make the activity to show soft input. 133 showSoftInputAndAssertImeShownOnDisplay(newDisplay.mId, imeTestActivitySession, stream); 134 135 // Assert the configuration of the IME window is the same as the configuration of the 136 // virtual display. 137 assertImeWindowAndDisplayConfiguration(mWmState.getImeWindowState(), newDisplay); 138 139 // Launch another activity on the default display. 140 imeTestActivitySession2.launchTestActivityOnDisplaySync( 141 ImeTestActivity2.class, DEFAULT_DISPLAY); 142 expectEvent(stream, editorMatcher("onStartInput", 143 imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 144 145 // Make the activity to show soft input. 146 showSoftInputAndAssertImeShownOnDisplay(DEFAULT_DISPLAY, imeTestActivitySession2, stream); 147 148 // Assert the configuration of the IME window is the same as the configuration of the 149 // default display. 150 assertImeWindowAndDisplayConfiguration(mWmState.getImeWindowState(), 151 mWmState.getDisplay(DEFAULT_DISPLAY)); 152 } 153 154 /** 155 * This checks that calling showSoftInput on the incorrect display, requiring the fallback IMM, 156 * will not drop the statsToken tracking the show request. 157 */ 158 @Test 159 @RequiresFlagsDisabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER) testFallbackImmMaintainsParameters()160 public void testFallbackImmMaintainsParameters() throws Exception { 161 try (var mockImeSession = createManagedMockImeSession(this); 162 TestActivitySession<ImeTestActivity> imeTestActivitySession = 163 createManagedTestActivitySession(); 164 var displaySession = createManagedVirtualDisplaySession()) { 165 final var newDisplay = displaySession.setSimulateDisplay(true).createDisplay(); 166 167 imeTestActivitySession.launchTestActivityOnDisplaySync( 168 ImeTestActivity.class, newDisplay.mId); 169 final var activity = imeTestActivitySession.getActivity(); 170 final var stream = mockImeSession.openEventStream(); 171 172 expectEvent(stream, editorMatcher("onStartInput", 173 activity.mEditText.getPrivateImeOptions()), TIMEOUT); 174 175 imeTestActivitySession.runOnMainSyncAndWait(() -> { 176 final var imm = activity.getApplicationContext() 177 .getSystemService(InputMethodManager.class); 178 imm.showSoftInput(activity.mEditText, 0 /* flags */); 179 }); 180 181 expectImeVisible(TIMEOUT); 182 PollingCheck.waitFor(() -> !mockImeSession.hasPendingImeVisibilityRequests(), 183 "No pending requests should remain after the IME is visible"); 184 } 185 } 186 187 @Test testImeApiForBug118341760()188 public void testImeApiForBug118341760() throws Exception { 189 final MockImeSession mockImeSession = createManagedMockImeSession(this); 190 final TestActivitySession<ImeTestActivityWithBrokenContextWrapper> imeTestActivitySession = 191 createManagedTestActivitySession(); 192 // Create a virtual display and launch an activity on it. 193 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 194 .setSimulateDisplay(true) 195 .createDisplay(); 196 imeTestActivitySession.launchTestActivityOnDisplaySync( 197 ImeTestActivityWithBrokenContextWrapper.class, newDisplay.mId); 198 199 final ImeTestActivityWithBrokenContextWrapper activity = 200 imeTestActivitySession.getActivity(); 201 final ImeEventStream stream = mockImeSession.openEventStream(); 202 final String privateImeOption = activity.getEditText().getPrivateImeOptions(); 203 expectEvent(stream, event -> { 204 if (!TextUtils.equals("onStartInput", event.getEventName())) { 205 return false; 206 } 207 final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo"); 208 return TextUtils.equals(editorInfo.packageName, mContext.getPackageName()) 209 && TextUtils.equals(editorInfo.privateImeOptions, privateImeOption); 210 }, TIMEOUT); 211 212 imeTestActivitySession.runOnMainSyncAndWait(() -> { 213 final InputMethodManager imm = activity.getSystemService(InputMethodManager.class); 214 assertTrue("InputMethodManager.isActive() should work", 215 imm.isActive(activity.getEditText())); 216 }); 217 } 218 219 @Test testImeWindowCanSwitchWhenTopFocusedDisplayChange()220 public void testImeWindowCanSwitchWhenTopFocusedDisplayChange() throws Exception { 221 // If config_perDisplayFocusEnabled, the focus will not move even if touching on 222 // the Activity in the different display. 223 assumeFalse(perDisplayFocusEnabled()); 224 225 final MockImeSession mockImeSession = createManagedMockImeSession(this); 226 final TestActivitySession<ImeTestActivity> imeTestActivitySession = 227 createManagedTestActivitySession(); 228 final TestActivitySession<ImeTestActivity2> imeTestActivitySession2 = 229 createManagedTestActivitySession(); 230 231 // Create a virtual display and launch an activity on virtual & default display. 232 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 233 .setSimulateDisplay(true) 234 .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL) 235 .createDisplay(); 236 imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class, 237 DEFAULT_DISPLAY); 238 imeTestActivitySession2.launchTestActivityOnDisplaySync(ImeTestActivity2.class, 239 newDisplay.mId); 240 241 final DisplayContent defDisplay = mWmState.getDisplay(DEFAULT_DISPLAY); 242 final ImeEventStream stream = mockImeSession.openEventStream(); 243 244 // Tap on the imeTestActivity task center instead of the display center because 245 // the activity might not be spanning the entire display 246 WindowManagerState.Task imeTestActivityTask = mWmState 247 .getTaskByActivity(imeTestActivitySession.getActivity().getComponentName()); 248 tapOnTaskCenter(imeTestActivityTask); 249 expectEvent(stream, editorMatcher("onStartInput", 250 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 251 showSoftInputAndAssertImeShownOnDisplay(defDisplay.mId, imeTestActivitySession, stream); 252 253 // Tap virtual display as top focused display & request focus on EditText to show 254 // soft input. 255 touchAndCancelOnDisplayCenterSync(newDisplay.mId); 256 expectEvent(stream, editorMatcher("onStartInput", 257 imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 258 showSoftInputAndAssertImeShownOnDisplay(newDisplay.mId, imeTestActivitySession2, stream); 259 260 // Tap on the imeTestActivity task center instead of the display center because 261 // the activity might not be spanning the entire display 262 imeTestActivityTask = mWmState 263 .getTaskByActivity(imeTestActivitySession.getActivity().getComponentName()); 264 tapOnTaskCenter(imeTestActivityTask); 265 expectEvent(stream, editorMatcher("onStartInput", 266 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 267 showSoftInputAndAssertImeShownOnDisplay(defDisplay.mId, imeTestActivitySession, stream); 268 } 269 270 /** 271 * Test that the IME can be shown in a different display (actually the default display) than 272 * the display on which the target IME application is shown. Then test several basic operations 273 * in {@link InputConnection}. 274 */ 275 @Test testCrossDisplayBasicImeOperations()276 public void testCrossDisplayBasicImeOperations() throws Exception { 277 final MockImeSession mockImeSession = createManagedMockImeSession(this); 278 final TestActivitySession<ImeTestActivity> imeTestActivitySession = 279 createManagedTestActivitySession(); 280 281 // Create a virtual display by app and assume the display should not show IME window. 282 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 283 .setPublicDisplay(true) 284 .createDisplay(); 285 SystemUtil.runWithShellPermissionIdentity( 286 () -> assertTrue("Display should not support showing IME window", 287 mTargetContext.getSystemService(WindowManager.class) 288 .getDisplayImePolicy(newDisplay.mId) 289 == DISPLAY_IME_POLICY_FALLBACK_DISPLAY)); 290 291 // Launch Ime test activity in virtual display. 292 imeTestActivitySession.launchTestActivityOnDisplay(ImeTestActivity.class, 293 newDisplay.mId); 294 final ImeEventStream stream = mockImeSession.openEventStream(); 295 296 // Expect onStartInput would be executed when user tapping on the 297 // non-system created display intentionally. 298 tapAndAssertEditorFocusedOnImeActivity(imeTestActivitySession, newDisplay.mId); 299 expectEvent(stream, editorMatcher("onStartInput", 300 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 301 302 // Verify the activity to show soft input on the default display. 303 showSoftInputAndAssertImeShownOnDisplay(DEFAULT_DISPLAY, imeTestActivitySession, stream); 304 305 // Commit text & make sure the input texts should be delivered to focused EditText on 306 // virtual display. 307 final EditText editText = imeTestActivitySession.getActivity().mEditText; 308 final String commitText = "test commit"; 309 expectCommand(stream, mockImeSession.callCommitText(commitText, 1), TIMEOUT); 310 imeTestActivitySession.runOnMainAndAssertWithTimeout( 311 () -> TextUtils.equals(commitText, editText.getText()), TIMEOUT, 312 "The input text should be delivered"); 313 314 // Since the IME and the IME target app are running in different displays, 315 // InputConnection#requestCursorUpdates() is not supported and it should return false. 316 // See InputMethodServiceTest#testOnUpdateCursorAnchorInfo() for the normal scenario. 317 final ImeCommand callCursorUpdates = mockImeSession.callRequestCursorUpdates( 318 InputConnection.CURSOR_UPDATE_IMMEDIATE); 319 assertFalse(expectCommand(stream, callCursorUpdates, TIMEOUT).getReturnBooleanValue()); 320 } 321 322 /** 323 * Test that the IME can be hidden with the {@link WindowManager#DISPLAY_IME_POLICY_HIDE} flag. 324 */ 325 @Test testDisplayPolicyImeHideImeOperation()326 public void testDisplayPolicyImeHideImeOperation() throws Exception { 327 final MockImeSession mockImeSession = createManagedMockImeSession(this); 328 final TestActivitySession<ImeTestActivity> imeTestActivitySession = 329 createManagedTestActivitySession(); 330 331 // Create a virtual display and launch an activity on virtual display. 332 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 333 .setDisplayImePolicy(DISPLAY_IME_POLICY_HIDE) 334 .setSimulateDisplay(true) 335 .createDisplay(); 336 337 // Launch Ime test activity and initial the editor focus on virtual display. 338 imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class, 339 newDisplay.mId); 340 341 // Verify the activity is launched to the secondary display. 342 final ComponentName imeTestActivityName = 343 imeTestActivitySession.getActivity().getComponentName(); 344 assertThat(mWmState.hasActivityInDisplay(newDisplay.mId, imeTestActivityName)).isTrue(); 345 346 // Verify invoking showSoftInput will be ignored when the display with the HIDE policy. 347 final ImeEventStream stream = mockImeSession.openEventStream(); 348 imeTestActivitySession.runOnMainSyncAndWait( 349 imeTestActivitySession.getActivity()::showSoftInput); 350 notExpectEvent(stream, editorMatcher("showSoftInput", 351 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), 352 NOT_EXPECT_TIMEOUT); 353 } 354 355 /** 356 * A regression test for Bug 273630528. 357 * 358 * Test that the IME on the editor activity with embedded in virtual display will be hidden 359 * after pressing the back key. 360 */ 361 @Test testHideImeWhenImeTargetOnEmbeddedVirtualDisplay()362 public void testHideImeWhenImeTargetOnEmbeddedVirtualDisplay() throws Exception { 363 final VirtualDisplaySession session = createManagedVirtualDisplaySession(); 364 final MockImeSession imeSession = createManagedMockImeSession(this); 365 final TestActivitySession<ImeTestActivity> imeActivitySession = 366 createManagedTestActivitySession(); 367 368 // Setup a virtual display embedded on an activity. 369 final DisplayContent dc = session 370 .setPublicDisplay(true) 371 .setSupportsTouch(true) 372 .createDisplay(); 373 374 // Launch a test activity on that virtual display and show IME by tapping the editor. 375 imeActivitySession.launchTestActivityOnDisplay(ImeTestActivity.class, dc.mId); 376 tapAndAssertEditorFocusedOnImeActivity(imeActivitySession, dc.mId); 377 final ImeEventStream stream = imeSession.openEventStream(); 378 final String marker = imeActivitySession.getActivity().mEditText.getPrivateImeOptions(); 379 expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT); 380 381 // Expect soft-keyboard becomes visible after requesting show IME. 382 showSoftInputAndAssertImeShownOnDisplay(DEFAULT_DISPLAY, imeActivitySession, stream); 383 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", 384 View.VISIBLE, TIMEOUT); 385 expectImeVisible(TIMEOUT); 386 387 // Pressing back key, expect soft-keyboard will become invisible. 388 pressBackButton(); 389 expectEvent(stream, hideSoftInputMatcher(), TIMEOUT); 390 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", 391 View.GONE, TIMEOUT); 392 expectImeInvisible(TIMEOUT); 393 } 394 395 @Test testImeWindowCanShownWhenActivityMovedToDisplay()396 public void testImeWindowCanShownWhenActivityMovedToDisplay() throws Exception { 397 // If config_perDisplayFocusEnabled, the focus will not move even if touching on 398 // the Activity in the different display. 399 assumeFalse(perDisplayFocusEnabled()); 400 401 // Launch a regular activity on default display at the test beginning to prevent the test 402 // may mis-touch the launcher icon that breaks the test expectation. 403 final TestActivitySession<Activities.RegularActivity> testActivitySession = 404 createManagedTestActivitySession(); 405 testActivitySession.launchTestActivityOnDisplaySync(Activities.RegularActivity.class, 406 DEFAULT_DISPLAY); 407 408 // Create a virtual display and launch an activity on virtual display. 409 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 410 .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL) 411 .setSimulateDisplay(true) 412 .createDisplay(); 413 414 // Leverage MockImeSession to ensure at least an IME exists as default. 415 final MockImeSession mockImeSession = createManagedMockImeSession(this); 416 final TestActivitySession<ImeTestActivity> imeTestActivitySession = 417 createManagedTestActivitySession(); 418 // Launch Ime test activity and initial the editor focus on virtual display. 419 imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class, 420 newDisplay.mId); 421 422 // Verify the activity is launched to the secondary display. 423 final ComponentName imeTestActivityName = 424 imeTestActivitySession.getActivity().getComponentName(); 425 assertThat(mWmState.hasActivityInDisplay(newDisplay.mId, imeTestActivityName)).isTrue(); 426 427 // Tap default display, assume a pointer-out-side event will happened to change the top 428 // display. 429 final DisplayContent defDisplay = mWmState.getDisplay(DEFAULT_DISPLAY); 430 tapOnDisplayCenter(defDisplay.mId); 431 mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY); 432 mWmState.assertValidity(); 433 434 // Reparent ImeTestActivity from virtual display to default display. 435 getLaunchActivityBuilder() 436 .setUseInstrumentation() 437 .setTargetActivity(imeTestActivitySession.getActivity().getComponentName()) 438 .setIntentFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 439 .allowMultipleInstances(false) 440 .setDisplayId(DEFAULT_DISPLAY).execute(); 441 waitAndAssertTopResumedActivity(imeTestActivitySession.getActivity().getComponentName(), 442 DEFAULT_DISPLAY, "Activity launched on default display and on top"); 443 444 // Activity is no longer on the secondary display 445 assertThat(mWmState.hasActivityInDisplay(newDisplay.mId, imeTestActivityName)).isFalse(); 446 447 // Tap on the imeTestActivity task center instead of the display center because 448 // the activity might not be spanning the entire display 449 final ImeEventStream stream = mockImeSession.openEventStream(); 450 final WindowManagerState.Task testActivityTask = mWmState 451 .getTaskByActivity(imeTestActivitySession.getActivity().getComponentName()); 452 tapOnTaskCenter(testActivityTask); 453 expectEvent(stream, editorMatcher("onStartInput", 454 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 455 456 // Verify the activity shows soft input on the default display. 457 showSoftInputAndAssertImeShownOnDisplay(DEFAULT_DISPLAY, imeTestActivitySession, stream); 458 } 459 460 @Test testNoConfigurationChangedWhenSwitchBetweenTwoIdenticalDisplays()461 public void testNoConfigurationChangedWhenSwitchBetweenTwoIdenticalDisplays() throws Exception { 462 // If config_perDisplayFocusEnabled, the focus will not move even if touching on 463 // the Activity in the different display. 464 assumeFalse(perDisplayFocusEnabled()); 465 466 // Create two displays with the same display metrics 467 final List<DisplayContent> newDisplays = createManagedVirtualDisplaySession() 468 .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL) 469 .setOwnContentOnly(true) 470 .setSimulateDisplay(true) 471 .setResizeDisplay(false) 472 .createDisplays(2); 473 final DisplayContent firstDisplay = newDisplays.get(0); 474 final DisplayContent secondDisplay = newDisplays.get(1); 475 476 // Skip if the test environment somehow didn't create 2 displays with identical size. 477 assumeTrue("Skip the test if the size of the created displays aren't identical", 478 firstDisplay.getDisplayRect().equals(secondDisplay.getDisplayRect())); 479 480 final TestActivitySession<ImeTestActivity2> imeTestActivitySession2 = 481 createManagedTestActivitySession(); 482 imeTestActivitySession2.launchTestActivityOnDisplaySync( 483 ImeTestActivity2.class, secondDisplay.mId); 484 485 // Make firstDisplay the top focus display. 486 touchAndCancelOnDisplayCenterSync(firstDisplay.mId); 487 488 mWmState.waitForWithAmState(state -> state.getFocusedDisplayId() == firstDisplay.mId, 489 "First display must be top focused."); 490 491 // Initialize IME test environment 492 final MockImeSession mockImeSession = createManagedMockImeSession(this); 493 final TestActivitySession<ImeTestActivity> imeTestActivitySession = 494 createManagedTestActivitySession(); 495 ImeEventStream stream = mockImeSession.openEventStream(); 496 // Filter out onConfigurationChanged events in case that IME is moved from the default 497 // display to the firstDisplay. 498 ImeEventStream configChangeVerifyStream = clearOnConfigurationChangedFromStream(stream); 499 500 imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class, 501 firstDisplay.mId); 502 // Wait until IME is ready for the IME client to call showSoftInput(). 503 expectEvent(stream, editorMatcher("onStartInput", 504 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 505 506 int imeDisplayId = expectCommand(stream, mockImeSession.callGetDisplayId(), 507 TIMEOUT).getReturnIntegerValue(); 508 assertThat(imeDisplayId).isEqualTo(firstDisplay.mId); 509 510 imeTestActivitySession.runOnMainSyncAndWait( 511 imeTestActivitySession.getActivity()::showSoftInput); 512 waitOrderedImeEventsThenAssertImeShown(stream, firstDisplay.mId, 513 event -> "showSoftInput".equals(event.getEventName())); 514 try { 515 // Launch Ime must not lead to screen size changes. 516 waitAndAssertImeNoScreenSizeChanged(configChangeVerifyStream); 517 518 final Rect currentBoundsOnFirstDisplay = expectCommand(stream, 519 mockImeSession.callGetCurrentWindowMetricsBounds(), TIMEOUT) 520 .getReturnParcelableValue(); 521 522 // Clear onConfigurationChanged events before IME moves to the secondary display to 523 // prevent flaky because IME may receive configuration updates which we don't care 524 // about. An example is CONFIG_KEYBOARD_HIDDEN. 525 configChangeVerifyStream = clearOnConfigurationChangedFromStream(stream); 526 527 // Tap secondDisplay to change it to the top focused display. 528 touchAndCancelOnDisplayCenterSync(firstDisplay.mId); 529 530 // Move ImeTestActivity from firstDisplay to secondDisplay. 531 getLaunchActivityBuilder() 532 .setUseInstrumentation() 533 .setTargetActivity(imeTestActivitySession.getActivity().getComponentName()) 534 .setIntentFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 535 .allowMultipleInstances(false) 536 .setDisplayId(secondDisplay.mId).execute(); 537 538 // Make sure ImeTestActivity is move from the firstDisplay to the secondDisplay 539 waitAndAssertTopResumedActivity(imeTestActivitySession.getActivity().getComponentName(), 540 secondDisplay.mId, "ImeTestActivity must be top-resumed on display#" 541 + secondDisplay.mId); 542 assertThat(mWmState.hasActivityInDisplay(firstDisplay.mId, 543 imeTestActivitySession.getActivity().getComponentName())).isFalse(); 544 // Wait until IME is ready for the IME client to call showSoftInput(). 545 expectEvent(stream, editorMatcher("onStartInput", 546 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), 547 TIMEOUT); 548 imeDisplayId = expectCommand(stream, mockImeSession.callGetDisplayId(), 549 TIMEOUT).getReturnIntegerValue(); 550 assertThat(imeDisplayId).isEqualTo(secondDisplay.mId); 551 552 // With the refactor, the additional show is not needed, as we already verified that 553 // the IME is showing 554 if (!android.view.inputmethod.Flags.refactorInsetsController()) { 555 // Show soft input again to trigger IME movement. 556 imeTestActivitySession.runOnMainSyncAndWait( 557 imeTestActivitySession.getActivity()::showSoftInput); 558 waitOrderedImeEventsThenAssertImeShown(stream, secondDisplay.mId, 559 event -> "showSoftInput".equals(event.getEventName())); 560 } 561 562 // Moving IME to the display with the same display metrics must not lead to 563 // screen size changes. 564 waitAndAssertImeNoScreenSizeChanged(configChangeVerifyStream); 565 566 final Rect currentBoundsOnSecondDisplay = expectCommand(stream, 567 mockImeSession.callGetCurrentWindowMetricsBounds(), TIMEOUT) 568 .getReturnParcelableValue(); 569 570 assertWithMessage("The current WindowMetrics bounds of IME must not be changed.") 571 .that(currentBoundsOnFirstDisplay).isEqualTo(currentBoundsOnSecondDisplay); 572 } catch (AssertionError e) { 573 mWmState.computeState(); 574 final Rect displayRect1 = mWmState.getDisplay(firstDisplay.mId).getDisplayRect(); 575 final Rect displayRect2 = mWmState.getDisplay(secondDisplay.mId).getDisplayRect(); 576 assumeTrue("Skip test since the size of one or both displays happens unexpected change", 577 displayRect1.equals(displayRect2)); 578 throw e; 579 } 580 } 581 582 public static class ImeTestActivity extends Activity { 583 EditText mEditText; 584 585 @Override onCreate(Bundle icicle)586 protected void onCreate(Bundle icicle) { 587 super.onCreate(icicle); 588 mEditText = new EditText(this); 589 // Set private IME option for editorMatcher to identify which TextView received 590 // onStartInput event. 591 resetPrivateImeOptionsIdentifier(); 592 final LinearLayout layout = new LinearLayout(this); 593 layout.setOrientation(LinearLayout.VERTICAL); 594 layout.addView(mEditText); 595 mEditText.requestFocus(); 596 // SOFT_INPUT_STATE_UNSPECIFIED may produced unexpected behavior for CTS. To make tests 597 // deterministic, using SOFT_INPUT_STATE_UNCHANGED instead. 598 setUnchangedSoftInputState(); 599 setContentView(layout); 600 } 601 showSoftInput()602 void showSoftInput() { 603 final InputMethodManager imm = getSystemService(InputMethodManager.class); 604 imm.showSoftInput(mEditText, 0); 605 } 606 resetPrivateImeOptionsIdentifier()607 void resetPrivateImeOptionsIdentifier() { 608 mEditText.setPrivateImeOptions( 609 getClass().getName() + "/" + Long.toString(SystemClock.elapsedRealtimeNanos())); 610 } 611 setUnchangedSoftInputState()612 private void setUnchangedSoftInputState() { 613 final Window window = getWindow(); 614 final int currentSoftInputMode = window.getAttributes().softInputMode; 615 final int newSoftInputMode = 616 (currentSoftInputMode & ~WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) 617 | SOFT_INPUT_STATE_UNCHANGED; 618 window.setSoftInputMode(newSoftInputMode); 619 } 620 } 621 622 public static class ImeTestActivity2 extends ImeTestActivity { } 623 624 public static final class ImeTestActivityWithBrokenContextWrapper extends Activity { 625 private EditText mEditText; 626 627 /** 628 * Emulates the behavior of certain {@link ContextWrapper} subclasses we found in the wild. 629 * 630 * <p> Certain {@link ContextWrapper} subclass in the wild delegate method calls to 631 * ApplicationContext except for {@link #getSystemService(String)}.</p> 632 * 633 **/ 634 private static final class Bug118341760ContextWrapper extends ContextWrapper { 635 private final Context mOriginalContext; 636 Bug118341760ContextWrapper(Context base)637 Bug118341760ContextWrapper(Context base) { 638 super(base.getApplicationContext()); 639 mOriginalContext = base; 640 } 641 642 /** 643 * Emulates the behavior of {@link ContextWrapper#getSystemService(String)} of certain 644 * {@link ContextWrapper} subclasses we found in the wild. 645 * 646 * @param name The name of the desired service. 647 * @return The service or {@code null} if the name does not exist. 648 */ 649 @Override getSystemService(String name)650 public Object getSystemService(String name) { 651 return mOriginalContext.getSystemService(name); 652 } 653 } 654 655 @Override onCreate(Bundle icicle)656 protected void onCreate(Bundle icicle) { 657 super.onCreate(icicle); 658 mEditText = new EditText(new Bug118341760ContextWrapper(this)); 659 // Use SystemClock.elapsedRealtimeNanos()) as a unique ID of this edit text. 660 mEditText.setPrivateImeOptions(Long.toString(SystemClock.elapsedRealtimeNanos())); 661 final LinearLayout layout = new LinearLayout(this); 662 layout.setOrientation(LinearLayout.VERTICAL); 663 layout.addView(mEditText); 664 mEditText.requestFocus(); 665 setContentView(layout); 666 } 667 getEditText()668 EditText getEditText() { 669 return mEditText; 670 } 671 } 672 assertImeWindowAndDisplayConfiguration( WindowState imeWinState, DisplayContent display)673 private void assertImeWindowAndDisplayConfiguration( 674 WindowState imeWinState, DisplayContent display) { 675 // The IME window should inherit the configuration from the IME DisplayArea. 676 final WindowManagerState.DisplayArea imeContainerDisplayArea = display.getImeContainer(); 677 final Configuration configurationForIme = imeWinState.getMergedOverrideConfiguration(); 678 final Configuration configurationForImeContainer = 679 imeContainerDisplayArea.getMergedOverrideConfiguration(); 680 final int displayDensityDpiForIme = configurationForIme.densityDpi; 681 final int displayDensityDpiForImeContainer = configurationForImeContainer.densityDpi; 682 final Rect displayBoundsForIme = configurationForIme.windowConfiguration.getBounds(); 683 final Rect displayBoundsForImeContainer = 684 configurationForImeContainer.windowConfiguration.getBounds(); 685 686 assertEquals("Display density not the same", 687 displayDensityDpiForImeContainer, displayDensityDpiForIme); 688 assertEquals("Display bounds not the same", 689 displayBoundsForImeContainer, displayBoundsForIme); 690 } 691 tapAndAssertEditorFocusedOnImeActivity( TestActivitySession<? extends ImeTestActivity> activitySession, int expectDisplayId)692 private void tapAndAssertEditorFocusedOnImeActivity( 693 TestActivitySession<? extends ImeTestActivity> activitySession, int expectDisplayId) { 694 final int[] location = new int[2]; 695 waitAndAssertActivityStateOnDisplay(activitySession.getActivity().getComponentName(), 696 STATE_RESUMED, expectDisplayId, 697 "ImeActivity failed to appear on display#" + expectDisplayId); 698 activitySession.runOnMainSyncAndWait(() -> { 699 final EditText editText = activitySession.getActivity().mEditText; 700 editText.getLocationOnScreen(location); 701 }); 702 final ComponentName expectComponent = activitySession.getActivity().getComponentName(); 703 tapOnDisplaySync(location[0], location[1], expectDisplayId); 704 mWmState.computeState(activitySession.getActivity().getComponentName()); 705 mWmState.assertFocusedAppOnDisplay("Activity not focus on the display", expectComponent, 706 expectDisplayId); 707 } 708 showSoftInputAndAssertImeShownOnDisplay(int displayId, TestActivitySession<? extends ImeTestActivity> activitySession, ImeEventStream stream)709 private void showSoftInputAndAssertImeShownOnDisplay(int displayId, 710 TestActivitySession<? extends ImeTestActivity> activitySession, ImeEventStream stream) 711 throws Exception { 712 activitySession.runOnMainSyncAndWait( 713 activitySession.getActivity()::showSoftInput); 714 expectEvent(stream, editorMatcher("onStartInputView", 715 activitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 716 // Assert the IME is shown on the expected display. 717 mWmState.waitAndAssertImeWindowShownOnDisplay(displayId); 718 } 719 } 720