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