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