1 /* 2 * Copyright (C) 2019 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; 18 19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; 20 import static android.server.wm.BarTestUtils.assumeHasBars; 21 import static android.server.wm.InputMethodVisibilityVerifier.expectImeInvisible; 22 import static android.server.wm.InputMethodVisibilityVerifier.expectImeVisible; 23 import static android.server.wm.MockImeHelper.createManagedMockImeSession; 24 import static android.server.wm.UiDeviceUtils.pressBackButton; 25 import static android.server.wm.WindowManagerState.STATE_RESUMED; 26 import static android.server.wm.app.Components.HOME_ACTIVITY; 27 import static android.server.wm.app.Components.SECONDARY_HOME_ACTIVITY; 28 import static android.server.wm.app.Components.SINGLE_HOME_ACTIVITY; 29 import static android.server.wm.app.Components.SINGLE_SECONDARY_HOME_ACTIVITY; 30 import static android.server.wm.app.Components.TEST_LIVE_WALLPAPER_SERVICE; 31 import static android.server.wm.app.Components.TestLiveWallpaperKeys.COMPONENT; 32 import static android.server.wm.app.Components.TestLiveWallpaperKeys.ENGINE_DISPLAY_ID; 33 import static android.view.Display.DEFAULT_DISPLAY; 34 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; 35 import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE; 36 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; 37 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED; 38 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; 39 40 import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher; 41 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand; 42 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent; 43 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEventWithKeyValue; 44 import static com.android.cts.mockime.ImeEventStreamTestUtils.hideSoftInputMatcher; 45 import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent; 46 47 import static com.google.common.truth.Truth.assertThat; 48 import static com.google.common.truth.Truth.assertWithMessage; 49 50 import static org.junit.Assert.assertEquals; 51 import static org.junit.Assert.assertFalse; 52 import static org.junit.Assert.assertTrue; 53 import static org.junit.Assume.assumeFalse; 54 import static org.junit.Assume.assumeTrue; 55 56 import android.app.Activity; 57 import android.content.ComponentName; 58 import android.content.Context; 59 import android.content.ContextWrapper; 60 import android.content.Intent; 61 import android.content.res.Configuration; 62 import android.content.res.Resources; 63 import android.graphics.Bitmap; 64 import android.graphics.Rect; 65 import android.os.Bundle; 66 import android.os.SystemClock; 67 import android.platform.test.annotations.Presubmit; 68 import android.server.wm.TestJournalProvider.TestJournalContainer; 69 import android.server.wm.WindowManagerState.DisplayContent; 70 import android.server.wm.WindowManagerState.WindowState; 71 import android.server.wm.intent.Activities; 72 import android.text.TextUtils; 73 import android.view.View; 74 import android.view.Window; 75 import android.view.WindowManager; 76 import android.view.inputmethod.EditorInfo; 77 import android.view.inputmethod.InputConnection; 78 import android.view.inputmethod.InputMethodManager; 79 import android.widget.EditText; 80 import android.widget.LinearLayout; 81 82 import com.android.compatibility.common.util.SystemUtil; 83 import com.android.compatibility.common.util.TestUtils; 84 import com.android.cts.mockime.ImeCommand; 85 import com.android.cts.mockime.ImeEventStream; 86 import com.android.cts.mockime.MockImeSession; 87 88 import org.junit.Before; 89 import org.junit.Test; 90 91 import java.util.List; 92 import java.util.concurrent.TimeUnit; 93 import java.util.stream.Collectors; 94 95 /** 96 * Build/Install/Run: 97 * atest CtsWindowManagerDeviceTestCases:MultiDisplaySystemDecorationTests 98 * 99 * This tests that verify the following should not be run for OEM device verification: 100 * Wallpaper added if display supports system decorations (and not added otherwise) 101 * Navigation bar is added if display supports system decorations (and not added otherwise) 102 * Secondary Home is shown if display supports system decorations (and not shown otherwise) 103 * IME is shown if display supports system decorations (and not shown otherwise) 104 */ 105 @Presubmit 106 @android.server.wm.annotation.Group3 107 public class MultiDisplaySystemDecorationTests extends MultiDisplayTestBase { 108 final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(2); 109 final long TIMEOUT = TimeUnit.SECONDS.toMillis(5); 110 111 @Before 112 @Override setUp()113 public void setUp() throws Exception { 114 super.setUp(); 115 116 assumeTrue(supportsMultiDisplay()); 117 assumeTrue(supportsSystemDecorsOnSecondaryDisplays()); 118 } 119 120 // Wallpaper related tests 121 /** 122 * Test WallpaperService.Engine#getDisplayContext can work on secondary display. 123 */ 124 @Test testWallpaperGetDisplayContext()125 public void testWallpaperGetDisplayContext() throws Exception { 126 assumeTrue(supportsLiveWallpaper()); 127 128 final ChangeWallpaperSession wallpaperSession = createManagedChangeWallpaperSession(); 129 final VirtualDisplaySession virtualDisplaySession = createManagedVirtualDisplaySession(); 130 131 TestJournalContainer.start(); 132 133 final DisplayContent newDisplay = virtualDisplaySession 134 .setSimulateDisplay(true).setShowSystemDecorations(true).createDisplay(); 135 136 wallpaperSession.setWallpaperComponent(TEST_LIVE_WALLPAPER_SERVICE); 137 final String TARGET_ENGINE_DISPLAY_ID = ENGINE_DISPLAY_ID + newDisplay.mId; 138 final TestJournalProvider.TestJournal journal = TestJournalContainer.get(COMPONENT); 139 TestUtils.waitUntil("Waiting for wallpaper engine bounded", 5 /* timeoutSecond */, 140 () -> journal.extras.getBoolean(TARGET_ENGINE_DISPLAY_ID)); 141 } 142 143 /** 144 * Tests that wallpaper shows on secondary displays. 145 */ 146 @Test testWallpaperShowOnSecondaryDisplays()147 public void testWallpaperShowOnSecondaryDisplays() { 148 assumeTrue(supportsWallpaper()); 149 150 final ChangeWallpaperSession wallpaperSession = createManagedChangeWallpaperSession(); 151 152 final DisplayContent untrustedDisplay = createManagedExternalDisplaySession() 153 .setPublicDisplay(true).setShowSystemDecorations(true).createVirtualDisplay(); 154 155 final DisplayContent decoredSystemDisplay = createManagedVirtualDisplaySession() 156 .setSimulateDisplay(true).setShowSystemDecorations(true).createDisplay(); 157 158 final Bitmap tmpWallpaper = wallpaperSession.getTestBitmap(); 159 wallpaperSession.setImageWallpaper(tmpWallpaper); 160 161 assertTrue("Wallpaper must be displayed on system owned display with system decor flag", 162 mWmState.waitForWithAmState( 163 state -> isWallpaperOnDisplay(state, decoredSystemDisplay.mId), 164 "wallpaper window to show")); 165 166 assertFalse("Wallpaper must not be displayed on the untrusted display", 167 isWallpaperOnDisplay(mWmState, untrustedDisplay.mId)); 168 } 169 isWallpaperOnDisplay(WindowManagerState windowManagerState, int displayId)170 private boolean isWallpaperOnDisplay(WindowManagerState windowManagerState, int displayId) { 171 return windowManagerState.getMatchingWindowType(TYPE_WALLPAPER).stream().anyMatch( 172 w -> w.getDisplayId() == displayId); 173 } 174 175 // Navigation bar related tests 176 // TODO(115978725): add runtime sys decor change test once we can do this. 177 /** 178 * Test that navigation bar should show on display with system decoration. 179 */ 180 @Test testNavBarShowingOnDisplayWithDecor()181 public void testNavBarShowingOnDisplayWithDecor() { 182 assumeHasBars(); 183 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 184 .setSimulateDisplay(true).setShowSystemDecorations(true).createDisplay(); 185 186 mWmState.waitAndAssertNavBarShownOnDisplay(newDisplay.mId); 187 } 188 189 /** 190 * Test that navigation bar should not show on display without system decoration. 191 */ 192 @Test testNavBarNotShowingOnDisplayWithoutDecor()193 public void testNavBarNotShowingOnDisplayWithoutDecor() { 194 assumeHasBars(); 195 // Wait for system decoration showing and record current nav states. 196 mWmState.waitForHomeActivityVisible(); 197 final List<WindowState> expected = mWmState.getAllNavigationBarStates(); 198 199 createManagedVirtualDisplaySession().setSimulateDisplay(true) 200 .setShowSystemDecorations(false).createDisplay(); 201 202 waitAndAssertNavBarStatesAreTheSame(expected); 203 } 204 205 /** 206 * Test that navigation bar should not show on private display even if the display 207 * supports system decoration. 208 */ 209 @Test testNavBarNotShowingOnPrivateDisplay()210 public void testNavBarNotShowingOnPrivateDisplay() { 211 assumeHasBars(); 212 // Wait for system decoration showing and record current nav states. 213 mWmState.waitForHomeActivityVisible(); 214 final List<WindowState> expected = mWmState.getAllNavigationBarStates(); 215 216 createManagedExternalDisplaySession().setPublicDisplay(false) 217 .setShowSystemDecorations(true).createVirtualDisplay(); 218 219 waitAndAssertNavBarStatesAreTheSame(expected); 220 } 221 waitAndAssertNavBarStatesAreTheSame(List<WindowState> expected)222 private void waitAndAssertNavBarStatesAreTheSame(List<WindowState> expected) { 223 // This is used to verify that we have nav bars shown on the same displays 224 // as before the test. 225 // 226 // The strategy is: 227 // Once a display with system ui decor support is created and a nav bar shows on the 228 // display, go back to verify whether the nav bar states are unchanged to verify that no nav 229 // bars were added to a display that was added before executing this method that shouldn't 230 // have nav bars (i.e. private or without system ui decor). 231 try (final VirtualDisplaySession secondDisplaySession = new VirtualDisplaySession()) { 232 final DisplayContent supportsSysDecorDisplay = secondDisplaySession 233 .setSimulateDisplay(true).setShowSystemDecorations(true).createDisplay(); 234 mWmState.waitAndAssertNavBarShownOnDisplay(supportsSysDecorDisplay.mId); 235 // This display has finished his task. Just close it. 236 } 237 238 mWmState.computeState(); 239 final List<WindowState> result = mWmState.getAllNavigationBarStates(); 240 241 assertEquals("The number of nav bars should be the same", expected.size(), result.size()); 242 243 mWmState.getDisplays().forEach(displayContent -> { 244 List<WindowState> navWindows = expected.stream().filter(ws -> 245 ws.getDisplayId() == displayContent.mId) 246 .collect(Collectors.toList()); 247 248 mWmState.waitAndAssertNavBarShownOnDisplay(displayContent.mId, navWindows.size()); 249 }); 250 } 251 252 // Secondary Home related tests 253 /** 254 * Tests launching a home activity on virtual display without system decoration support. 255 */ 256 @Test testLaunchHomeActivityOnSecondaryDisplayWithoutDecorations()257 public void testLaunchHomeActivityOnSecondaryDisplayWithoutDecorations() { 258 createManagedHomeActivitySession(SECONDARY_HOME_ACTIVITY); 259 260 // Create new virtual display without system decoration support. 261 final DisplayContent newDisplay = createManagedExternalDisplaySession() 262 .createVirtualDisplay(); 263 264 // Secondary home activity can't be launched on the display without system decoration 265 // support. 266 assertEquals("No stacks on newly launched virtual display", 0, newDisplay.mRootTasks.size()); 267 } 268 269 /** Tests launching a home activity on untrusted virtual display. */ 270 @Test testLaunchHomeActivityOnUntrustedVirtualSecondaryDisplay()271 public void testLaunchHomeActivityOnUntrustedVirtualSecondaryDisplay() { 272 createManagedHomeActivitySession(SECONDARY_HOME_ACTIVITY); 273 274 // Create new virtual display with system decoration support flag. 275 final DisplayContent newDisplay = createManagedExternalDisplaySession() 276 .setPublicDisplay(true).setShowSystemDecorations(true).createVirtualDisplay(); 277 278 // Secondary home activity can't be launched on the untrusted virtual display. 279 assertEquals("No stacks on untrusted virtual display", 0, newDisplay.mRootTasks.size()); 280 } 281 282 /** 283 * Tests launching a single instance home activity on virtual display with system decoration 284 * support. 285 */ 286 @Test testLaunchSingleHomeActivityOnDisplayWithDecorations()287 public void testLaunchSingleHomeActivityOnDisplayWithDecorations() { 288 createManagedHomeActivitySession(SINGLE_HOME_ACTIVITY); 289 290 // If default home doesn't support multi-instance, default secondary home activity 291 // should be automatically launched on the new display. 292 assertSecondaryHomeResumedOnNewDisplay(getDefaultSecondaryHomeComponent()); 293 } 294 295 /** 296 * Tests launching a single instance home activity with SECONDARY_HOME on virtual display with 297 * system decoration support. 298 */ 299 @Test testLaunchSingleSecondaryHomeActivityOnDisplayWithDecorations()300 public void testLaunchSingleSecondaryHomeActivityOnDisplayWithDecorations() { 301 createManagedHomeActivitySession(SINGLE_SECONDARY_HOME_ACTIVITY); 302 303 // If provided secondary home doesn't support multi-instance, default secondary home 304 // activity should be automatically launched on the new display. 305 assertSecondaryHomeResumedOnNewDisplay(getDefaultSecondaryHomeComponent()); 306 } 307 308 /** 309 * Tests launching a multi-instance home activity on virtual display with system decoration 310 * support. 311 */ 312 @Test testLaunchHomeActivityOnDisplayWithDecorations()313 public void testLaunchHomeActivityOnDisplayWithDecorations() { 314 createManagedHomeActivitySession(HOME_ACTIVITY); 315 316 // If default home doesn't have SECONDARY_HOME category, default secondary home 317 // activity should be automatically launched on the new display. 318 assertSecondaryHomeResumedOnNewDisplay(getDefaultSecondaryHomeComponent()); 319 } 320 321 /** 322 * Tests launching a multi-instance home activity with SECONDARY_HOME on virtual display with 323 * system decoration support. 324 */ 325 @Test testLaunchSecondaryHomeActivityOnDisplayWithDecorations()326 public void testLaunchSecondaryHomeActivityOnDisplayWithDecorations() { 327 createManagedHomeActivitySession(SECONDARY_HOME_ACTIVITY); 328 boolean useSystemProvidedLauncher = mContext.getResources().getBoolean( 329 Resources.getSystem().getIdentifier("config_useSystemProvidedLauncherForSecondary", 330 "bool", "android")); 331 332 if (useSystemProvidedLauncher) { 333 // Default secondary home activity should be automatically launched on the new display 334 // if forced by the config. 335 assertSecondaryHomeResumedOnNewDisplay(getDefaultSecondaryHomeComponent()); 336 } else { 337 // Provided secondary home activity should be automatically launched on the new display. 338 assertSecondaryHomeResumedOnNewDisplay(SECONDARY_HOME_ACTIVITY); 339 } 340 } 341 assertSecondaryHomeResumedOnNewDisplay(ComponentName homeComponentName)342 private void assertSecondaryHomeResumedOnNewDisplay(ComponentName homeComponentName) { 343 // Create new simulated display with system decoration support. 344 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 345 .setSimulateDisplay(true) 346 .setShowSystemDecorations(true) 347 .createDisplay(); 348 349 waitAndAssertActivityStateOnDisplay(homeComponentName, STATE_RESUMED, 350 newDisplay.mId, "Activity launched on secondary display must be resumed"); 351 352 tapOnDisplayCenter(newDisplay.mId); 353 assertEquals("Top activity must be home type", ACTIVITY_TYPE_HOME, 354 mWmState.getFrontRootTaskActivityType(newDisplay.mId)); 355 } 356 357 // IME related tests 358 @Test testImeWindowCanSwitchToDifferentDisplays()359 public void testImeWindowCanSwitchToDifferentDisplays() throws Exception { 360 assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme()); 361 362 final MockImeSession mockImeSession = createManagedMockImeSession(this); 363 final TestActivitySession<ImeTestActivity> imeTestActivitySession = 364 createManagedTestActivitySession(); 365 final TestActivitySession<ImeTestActivity2> imeTestActivitySession2 = 366 createManagedTestActivitySession(); 367 368 // Create a virtual display and launch an activity on it. 369 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 370 .setShowSystemDecorations(true) 371 .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL) 372 .setSimulateDisplay(true) 373 .createDisplay(); 374 375 final ImeEventStream stream = mockImeSession.openEventStream(); 376 377 imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class, 378 newDisplay.mId); 379 380 expectEvent(stream, editorMatcher("onStartInput", 381 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 382 383 // Make the activity to show soft input. 384 showSoftInputAndAssertImeShownOnDisplay(newDisplay.mId, imeTestActivitySession, stream); 385 386 // Assert the configuration of the IME window is the same as the configuration of the 387 // virtual display. 388 assertImeWindowAndDisplayConfiguration(mWmState.getImeWindowState(), newDisplay); 389 390 // Launch another activity on the default display. 391 imeTestActivitySession2.launchTestActivityOnDisplaySync( 392 ImeTestActivity2.class, DEFAULT_DISPLAY); 393 expectEvent(stream, editorMatcher("onStartInput", 394 imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 395 396 // Make the activity to show soft input. 397 showSoftInputAndAssertImeShownOnDisplay(DEFAULT_DISPLAY, imeTestActivitySession2, stream); 398 399 // Assert the configuration of the IME window is the same as the configuration of the 400 // default display. 401 assertImeWindowAndDisplayConfiguration(mWmState.getImeWindowState(), 402 mWmState.getDisplay(DEFAULT_DISPLAY)); 403 } 404 405 @Test testImeApiForBug118341760()406 public void testImeApiForBug118341760() throws Exception { 407 assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme()); 408 409 final long TIMEOUT_START_INPUT = TimeUnit.SECONDS.toMillis(5); 410 411 final MockImeSession mockImeSession = createManagedMockImeSession(this); 412 final TestActivitySession<ImeTestActivityWithBrokenContextWrapper> imeTestActivitySession = 413 createManagedTestActivitySession(); 414 // Create a virtual display and launch an activity on it. 415 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 416 .setShowSystemDecorations(true) 417 .setSimulateDisplay(true) 418 .createDisplay(); 419 imeTestActivitySession.launchTestActivityOnDisplaySync( 420 ImeTestActivityWithBrokenContextWrapper.class, newDisplay.mId); 421 422 final ImeTestActivityWithBrokenContextWrapper activity = 423 imeTestActivitySession.getActivity(); 424 final ImeEventStream stream = mockImeSession.openEventStream(); 425 final String privateImeOption = activity.getEditText().getPrivateImeOptions(); 426 expectEvent(stream, event -> { 427 if (!TextUtils.equals("onStartInput", event.getEventName())) { 428 return false; 429 } 430 final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo"); 431 return TextUtils.equals(editorInfo.packageName, mContext.getPackageName()) 432 && TextUtils.equals(editorInfo.privateImeOptions, privateImeOption); 433 }, TIMEOUT_START_INPUT); 434 435 imeTestActivitySession.runOnMainSyncAndWait(() -> { 436 final InputMethodManager imm = activity.getSystemService(InputMethodManager.class); 437 assertTrue("InputMethodManager.isActive() should work", 438 imm.isActive(activity.getEditText())); 439 }); 440 } 441 442 @Test testImeWindowCanSwitchWhenTopFocusedDisplayChange()443 public void testImeWindowCanSwitchWhenTopFocusedDisplayChange() throws Exception { 444 // If config_perDisplayFocusEnabled, the focus will not move even if touching on 445 // the Activity in the different display. 446 assumeFalse(perDisplayFocusEnabled()); 447 assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme()); 448 449 final MockImeSession mockImeSession = createManagedMockImeSession(this); 450 final TestActivitySession<ImeTestActivity> imeTestActivitySession = 451 createManagedTestActivitySession(); 452 final TestActivitySession<ImeTestActivity2> imeTestActivitySession2 = 453 createManagedTestActivitySession(); 454 455 // Create a virtual display and launch an activity on virtual & default display. 456 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 457 .setShowSystemDecorations(true) 458 .setSimulateDisplay(true) 459 .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL) 460 .createDisplay(); 461 imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class, 462 DEFAULT_DISPLAY); 463 imeTestActivitySession2.launchTestActivityOnDisplaySync(ImeTestActivity2.class, 464 newDisplay.mId); 465 466 final DisplayContent defDisplay = mWmState.getDisplay(DEFAULT_DISPLAY); 467 final ImeEventStream stream = mockImeSession.openEventStream(); 468 469 // Tap on the imeTestActivity task center instead of the display center because 470 // the activity might not be spanning the entire display 471 WindowManagerState.Task imeTestActivityTask = mWmState 472 .getTaskByActivity(imeTestActivitySession.getActivity().getComponentName()); 473 tapOnTaskCenter(imeTestActivityTask); 474 expectEvent(stream, editorMatcher("onStartInput", 475 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 476 showSoftInputAndAssertImeShownOnDisplay(defDisplay.mId, imeTestActivitySession, stream); 477 478 // Tap virtual display as top focused display & request focus on EditText to show 479 // soft input. 480 tapOnDisplayCenter(newDisplay.mId); 481 expectEvent(stream, editorMatcher("onStartInput", 482 imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 483 showSoftInputAndAssertImeShownOnDisplay(newDisplay.mId, imeTestActivitySession2, stream); 484 485 // Tap on the imeTestActivity task center instead of the display center because 486 // the activity might not be spanning the entire display 487 imeTestActivityTask = mWmState 488 .getTaskByActivity(imeTestActivitySession.getActivity().getComponentName()); 489 tapOnTaskCenter(imeTestActivityTask); 490 expectEvent(stream, editorMatcher("onStartInput", 491 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 492 showSoftInputAndAssertImeShownOnDisplay(defDisplay.mId, imeTestActivitySession, stream); 493 } 494 495 /** 496 * Test that the IME can be shown in a different display (actually the default display) than 497 * the display on which the target IME application is shown. Then test several basic operations 498 * in {@link InputConnection}. 499 */ 500 @Test testCrossDisplayBasicImeOperations()501 public void testCrossDisplayBasicImeOperations() throws Exception { 502 assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme()); 503 504 final MockImeSession mockImeSession = createManagedMockImeSession(this); 505 final TestActivitySession<ImeTestActivity> imeTestActivitySession = 506 createManagedTestActivitySession(); 507 508 // Create a virtual display by app and assume the display should not show IME window. 509 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 510 .setPublicDisplay(true) 511 .createDisplay(); 512 SystemUtil.runWithShellPermissionIdentity( 513 () -> assertTrue("Display should not support showing IME window", 514 mTargetContext.getSystemService(WindowManager.class) 515 .getDisplayImePolicy(newDisplay.mId) 516 == DISPLAY_IME_POLICY_FALLBACK_DISPLAY)); 517 518 // Launch Ime test activity in virtual display. 519 imeTestActivitySession.launchTestActivityOnDisplay(ImeTestActivity.class, 520 newDisplay.mId); 521 final ImeEventStream stream = mockImeSession.openEventStream(); 522 523 // Expect onStartInput would be executed when user tapping on the 524 // non-system created display intentionally. 525 tapAndAssertEditorFocusedOnImeActivity(imeTestActivitySession, newDisplay.mId); 526 expectEvent(stream, editorMatcher("onStartInput", 527 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 528 529 // Verify the activity to show soft input on the default display. 530 showSoftInputAndAssertImeShownOnDisplay(DEFAULT_DISPLAY, imeTestActivitySession, stream); 531 532 // Commit text & make sure the input texts should be delivered to focused EditText on 533 // virtual display. 534 final EditText editText = imeTestActivitySession.getActivity().mEditText; 535 final String commitText = "test commit"; 536 expectCommand(stream, mockImeSession.callCommitText(commitText, 1), TIMEOUT); 537 imeTestActivitySession.runOnMainAndAssertWithTimeout( 538 () -> TextUtils.equals(commitText, editText.getText()), TIMEOUT, 539 "The input text should be delivered"); 540 541 // Since the IME and the IME target app are running in different displays, 542 // InputConnection#requestCursorUpdates() is not supported and it should return false. 543 // See InputMethodServiceTest#testOnUpdateCursorAnchorInfo() for the normal scenario. 544 final ImeCommand callCursorUpdates = mockImeSession.callRequestCursorUpdates( 545 InputConnection.CURSOR_UPDATE_IMMEDIATE); 546 assertFalse(expectCommand(stream, callCursorUpdates, TIMEOUT).getReturnBooleanValue()); 547 } 548 549 /** 550 * Test that the IME can be hidden with the {@link WindowManager#DISPLAY_IME_POLICY_HIDE} flag. 551 */ 552 @Test testDisplayPolicyImeHideImeOperation()553 public void testDisplayPolicyImeHideImeOperation() throws Exception { 554 assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme()); 555 556 final MockImeSession mockImeSession = createManagedMockImeSession(this); 557 final TestActivitySession<ImeTestActivity> imeTestActivitySession = 558 createManagedTestActivitySession(); 559 560 // Create a virtual display and launch an activity on virtual display. 561 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 562 .setShowSystemDecorations(true) 563 .setDisplayImePolicy(DISPLAY_IME_POLICY_HIDE) 564 .setSimulateDisplay(true) 565 .createDisplay(); 566 567 // Launch Ime test activity and initial the editor focus on virtual display. 568 imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class, 569 newDisplay.mId); 570 571 // Verify the activity is launched to the secondary display. 572 final ComponentName imeTestActivityName = 573 imeTestActivitySession.getActivity().getComponentName(); 574 assertThat(mWmState.hasActivityInDisplay(newDisplay.mId, imeTestActivityName)).isTrue(); 575 576 // Verify invoking showSoftInput will be ignored when the display with the HIDE policy. 577 final ImeEventStream stream = mockImeSession.openEventStream(); 578 imeTestActivitySession.runOnMainSyncAndWait( 579 imeTestActivitySession.getActivity()::showSoftInput); 580 notExpectEvent(stream, editorMatcher("showSoftInput", 581 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), 582 NOT_EXPECT_TIMEOUT); 583 } 584 585 /** 586 * A regression test for Bug 273630528. 587 * 588 * Test that the IME on the editor activity with embedded in virtual display will be hidden 589 * after pressing the back key. 590 */ 591 @Test testHideImeWhenImeTargetOnEmbeddedVirtualDisplay()592 public void testHideImeWhenImeTargetOnEmbeddedVirtualDisplay() throws Exception { 593 assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme()); 594 595 final VirtualDisplaySession session = createManagedVirtualDisplaySession(); 596 final MockImeSession imeSession = createManagedMockImeSession(this); 597 final TestActivitySession<ImeTestActivity> imeActivitySession = 598 createManagedTestActivitySession(); 599 600 // Setup a virtual display embedded on an activity. 601 final WindowManagerState.DisplayContent dc = session 602 .setPublicDisplay(true) 603 .setSupportsTouch(true) 604 .createDisplay(); 605 606 // Launch a test activity on that virtual display and show IME by tapping the editor. 607 imeActivitySession.launchTestActivityOnDisplay(ImeTestActivity.class, dc.mId); 608 tapAndAssertEditorFocusedOnImeActivity(imeActivitySession, dc.mId); 609 final ImeEventStream stream = imeSession.openEventStream(); 610 final String marker = imeActivitySession.getActivity().mEditText.getPrivateImeOptions(); 611 expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT); 612 613 // Expect soft-keyboard becomes visible after requesting show IME. 614 showSoftInputAndAssertImeShownOnDisplay(DEFAULT_DISPLAY, imeActivitySession, stream); 615 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", 616 View.VISIBLE, TIMEOUT); 617 expectImeVisible(TIMEOUT); 618 619 // Pressing back key, expect soft-keyboard will become invisible. 620 pressBackButton(); 621 expectEvent(stream, hideSoftInputMatcher(), TIMEOUT); 622 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible", 623 View.GONE, TIMEOUT); 624 expectImeInvisible(TIMEOUT); 625 } 626 627 /** 628 * Test that the IME remains hidden with the {@link WindowManager#DISPLAY_IME_POLICY_HIDE} flag 629 * if the user taps the EditText on displays with no system decorations. 630 */ 631 @Test testDisplayPolicyImeHideImeNoSystemDecorations()632 public void testDisplayPolicyImeHideImeNoSystemDecorations() throws Exception { 633 assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme()); 634 635 final MockImeSession mockImeSession = createManagedMockImeSession(this); 636 final ImeEventStream stream = mockImeSession.openEventStream(); 637 638 // Create a virtual display with the policy to hide the IME. 639 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 640 .setShowSystemDecorations(false) 641 .setDisplayImePolicy(DISPLAY_IME_POLICY_HIDE) 642 .setSimulateDisplay(true) 643 .createDisplay(); 644 645 SystemUtil.runWithShellPermissionIdentity( 646 () -> assertTrue("Display should not support showing IME window", 647 mTargetContext.getSystemService(WindowManager.class) 648 .getDisplayImePolicy(newDisplay.mId) 649 == DISPLAY_IME_POLICY_HIDE)); 650 651 final TestActivitySession<ImeTestActivity> imeTestActivitySession = 652 createManagedTestActivitySession(); 653 654 // Launch Ime test activity and initial the editor focus on virtual display. 655 imeTestActivitySession.launchTestActivityOnDisplay(ImeTestActivity.class, 656 newDisplay.mId); 657 658 // Expect no onStartInput and the activity does not show soft input when user taps the 659 // editor on the display with the HIDE policy. 660 tapAndAssertEditorFocusedOnImeActivity(imeTestActivitySession, newDisplay.mId); 661 notExpectEvent(stream, editorMatcher("onStartInput", 662 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), 663 NOT_EXPECT_TIMEOUT); 664 InputMethodVisibilityVerifier.expectImeInvisible(NOT_EXPECT_TIMEOUT); 665 } 666 667 @Test testImeWindowCanShownWhenActivityMovedToDisplay()668 public void testImeWindowCanShownWhenActivityMovedToDisplay() throws Exception { 669 // If config_perDisplayFocusEnabled, the focus will not move even if touching on 670 // the Activity in the different display. 671 assumeFalse(perDisplayFocusEnabled()); 672 assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme()); 673 674 // Launch a regular activity on default display at the test beginning to prevent the test 675 // may mis-touch the launcher icon that breaks the test expectation. 676 final TestActivitySession<Activities.RegularActivity> testActivitySession = 677 createManagedTestActivitySession(); 678 testActivitySession.launchTestActivityOnDisplaySync(Activities.RegularActivity.class, 679 DEFAULT_DISPLAY); 680 681 // Create a virtual display and launch an activity on virtual display. 682 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 683 .setShowSystemDecorations(true) 684 .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL) 685 .setSimulateDisplay(true) 686 .createDisplay(); 687 688 // Leverage MockImeSession to ensure at least an IME exists as default. 689 final MockImeSession mockImeSession = createManagedMockImeSession(this); 690 final TestActivitySession<ImeTestActivity> imeTestActivitySession = 691 createManagedTestActivitySession(); 692 // Launch Ime test activity and initial the editor focus on virtual display. 693 imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class, 694 newDisplay.mId); 695 696 // Verify the activity is launched to the secondary display. 697 final ComponentName imeTestActivityName = 698 imeTestActivitySession.getActivity().getComponentName(); 699 assertThat(mWmState.hasActivityInDisplay(newDisplay.mId, imeTestActivityName)).isTrue(); 700 701 // Tap default display, assume a pointer-out-side event will happened to change the top 702 // display. 703 final DisplayContent defDisplay = mWmState.getDisplay(DEFAULT_DISPLAY); 704 tapOnDisplayCenter(defDisplay.mId); 705 mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY); 706 mWmState.assertValidity(); 707 708 // Reparent ImeTestActivity from virtual display to default display. 709 getLaunchActivityBuilder() 710 .setUseInstrumentation() 711 .setTargetActivity(imeTestActivitySession.getActivity().getComponentName()) 712 .setIntentFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 713 .allowMultipleInstances(false) 714 .setDisplayId(DEFAULT_DISPLAY).execute(); 715 waitAndAssertTopResumedActivity(imeTestActivitySession.getActivity().getComponentName(), 716 DEFAULT_DISPLAY, "Activity launched on default display and on top"); 717 718 // Activity is no longer on the secondary display 719 assertThat(mWmState.hasActivityInDisplay(newDisplay.mId, imeTestActivityName)).isFalse(); 720 721 // Tap on the imeTestActivity task center instead of the display center because 722 // the activity might not be spanning the entire display 723 final ImeEventStream stream = mockImeSession.openEventStream(); 724 final WindowManagerState.Task testActivityTask = mWmState 725 .getTaskByActivity(imeTestActivitySession.getActivity().getComponentName()); 726 tapOnTaskCenter(testActivityTask); 727 expectEvent(stream, editorMatcher("onStartInput", 728 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 729 730 // Verify the activity shows soft input on the default display. 731 showSoftInputAndAssertImeShownOnDisplay(DEFAULT_DISPLAY, imeTestActivitySession, stream); 732 } 733 734 @Test testNoConfigurationChangedWhenSwitchBetweenTwoIdenticalDisplays()735 public void testNoConfigurationChangedWhenSwitchBetweenTwoIdenticalDisplays() throws Exception { 736 // If config_perDisplayFocusEnabled, the focus will not move even if touching on 737 // the Activity in the different display. 738 assumeFalse(perDisplayFocusEnabled()); 739 assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme()); 740 741 // Create two displays with the same display metrics 742 final List<DisplayContent> newDisplays = createManagedVirtualDisplaySession() 743 .setShowSystemDecorations(true) 744 .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL) 745 .setSimulateDisplay(true) 746 .setResizeDisplay(false) 747 .createDisplays(2); 748 final DisplayContent firstDisplay = newDisplays.get(0); 749 final DisplayContent secondDisplay = newDisplays.get(1); 750 751 // Skip if the test environment somehow didn't create 2 displays with identical size. 752 assumeTrue("Skip the test if the size of the created displays aren't identical", 753 firstDisplay.getDisplayRect().equals(secondDisplay.getDisplayRect())); 754 755 // Make firstDisplay the top focus display. 756 tapOnDisplayCenter(firstDisplay.mId); 757 758 mWmState.waitForWithAmState(state -> state.getFocusedDisplayId() == firstDisplay.mId, 759 "First display must be top focused."); 760 761 // Initialize IME test environment 762 final MockImeSession mockImeSession = createManagedMockImeSession(this); 763 final TestActivitySession<ImeTestActivity> imeTestActivitySession = 764 createManagedTestActivitySession(); 765 ImeEventStream stream = mockImeSession.openEventStream(); 766 // Filter out onConfigurationChanged events in case that IME is moved from the default 767 // display to the firstDisplay. 768 ImeEventStream configChangeVerifyStream = clearOnConfigurationChangedFromStream(stream); 769 770 imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class, 771 firstDisplay.mId); 772 imeTestActivitySession.runOnMainSyncAndWait( 773 imeTestActivitySession.getActivity()::showSoftInput); 774 775 waitOrderedImeEventsThenAssertImeShown(stream, firstDisplay.mId, 776 editorMatcher("onStartInput", 777 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), 778 event -> "showSoftInput".equals(event.getEventName())); 779 try { 780 // Launch Ime must not lead to screen size changes. 781 waitAndAssertImeNoScreenSizeChanged(configChangeVerifyStream); 782 783 final Rect currentBoundsOnFirstDisplay = expectCommand(stream, 784 mockImeSession.callGetCurrentWindowMetricsBounds(), TIMEOUT) 785 .getReturnParcelableValue(); 786 787 // Clear onConfigurationChanged events before IME moves to the secondary display to 788 // prevent flaky because IME may receive configuration updates which we don't care 789 // about. An example is CONFIG_KEYBOARD_HIDDEN. 790 configChangeVerifyStream = clearOnConfigurationChangedFromStream(stream); 791 792 // Tap secondDisplay to change it to the top focused display. 793 tapOnDisplayCenter(secondDisplay.mId); 794 795 // Move ImeTestActivity from firstDisplay to secondDisplay. 796 getLaunchActivityBuilder() 797 .setUseInstrumentation() 798 .setTargetActivity(imeTestActivitySession.getActivity().getComponentName()) 799 .setIntentFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 800 .allowMultipleInstances(false) 801 .setDisplayId(secondDisplay.mId).execute(); 802 803 // Make sure ImeTestActivity is move from the firstDisplay to the secondDisplay 804 waitAndAssertTopResumedActivity(imeTestActivitySession.getActivity().getComponentName(), 805 secondDisplay.mId, "ImeTestActivity must be top-resumed on display#" 806 + secondDisplay.mId); 807 assertThat(mWmState.hasActivityInDisplay(firstDisplay.mId, 808 imeTestActivitySession.getActivity().getComponentName())).isFalse(); 809 810 // Show soft input again to trigger IME movement. 811 imeTestActivitySession.runOnMainSyncAndWait( 812 imeTestActivitySession.getActivity()::showSoftInput); 813 814 waitOrderedImeEventsThenAssertImeShown(stream, secondDisplay.mId, 815 editorMatcher("onStartInput", 816 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), 817 event -> "showSoftInput".equals(event.getEventName())); 818 819 // Moving IME to the display with the same display metrics must not lead to 820 // screen size changes. 821 waitAndAssertImeNoScreenSizeChanged(configChangeVerifyStream); 822 823 final Rect currentBoundsOnSecondDisplay = expectCommand(stream, 824 mockImeSession.callGetCurrentWindowMetricsBounds(), TIMEOUT) 825 .getReturnParcelableValue(); 826 827 assertWithMessage("The current WindowMetrics bounds of IME must not be changed.") 828 .that(currentBoundsOnFirstDisplay).isEqualTo(currentBoundsOnSecondDisplay); 829 } catch (AssertionError e) { 830 mWmState.computeState(); 831 final Rect displayRect1 = mWmState.getDisplay(firstDisplay.mId).getDisplayRect(); 832 final Rect displayRect2 = mWmState.getDisplay(secondDisplay.mId).getDisplayRect(); 833 assumeTrue("Skip test since the size of one or both displays happens unexpected change", 834 displayRect1.equals(displayRect2)); 835 throw e; 836 } 837 } 838 839 public static class ImeTestActivity extends Activity { 840 EditText mEditText; 841 842 @Override onCreate(Bundle icicle)843 protected void onCreate(Bundle icicle) { 844 super.onCreate(icicle); 845 mEditText = new EditText(this); 846 // Set private IME option for editorMatcher to identify which TextView received 847 // onStartInput event. 848 resetPrivateImeOptionsIdentifier(); 849 final LinearLayout layout = new LinearLayout(this); 850 layout.setOrientation(LinearLayout.VERTICAL); 851 layout.addView(mEditText); 852 mEditText.requestFocus(); 853 // SOFT_INPUT_STATE_UNSPECIFIED may produced unexpected behavior for CTS. To make tests 854 // deterministic, using SOFT_INPUT_STATE_UNCHANGED instead. 855 setUnchangedSoftInputState(); 856 setContentView(layout); 857 } 858 showSoftInput()859 void showSoftInput() { 860 final InputMethodManager imm = getSystemService(InputMethodManager.class); 861 imm.showSoftInput(mEditText, 0); 862 } 863 resetPrivateImeOptionsIdentifier()864 void resetPrivateImeOptionsIdentifier() { 865 mEditText.setPrivateImeOptions( 866 getClass().getName() + "/" + Long.toString(SystemClock.elapsedRealtimeNanos())); 867 } 868 setUnchangedSoftInputState()869 private void setUnchangedSoftInputState() { 870 final Window window = getWindow(); 871 final int currentSoftInputMode = window.getAttributes().softInputMode; 872 final int newSoftInputMode = 873 (currentSoftInputMode & ~WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) 874 | SOFT_INPUT_STATE_UNCHANGED; 875 window.setSoftInputMode(newSoftInputMode); 876 } 877 } 878 879 public static class ImeTestActivity2 extends ImeTestActivity { } 880 881 public static final class ImeTestActivityWithBrokenContextWrapper extends Activity { 882 private EditText mEditText; 883 884 /** 885 * Emulates the behavior of certain {@link ContextWrapper} subclasses we found in the wild. 886 * 887 * <p> Certain {@link ContextWrapper} subclass in the wild delegate method calls to 888 * ApplicationContext except for {@link #getSystemService(String)}.</p> 889 * 890 **/ 891 private static final class Bug118341760ContextWrapper extends ContextWrapper { 892 private final Context mOriginalContext; 893 Bug118341760ContextWrapper(Context base)894 Bug118341760ContextWrapper(Context base) { 895 super(base.getApplicationContext()); 896 mOriginalContext = base; 897 } 898 899 /** 900 * Emulates the behavior of {@link ContextWrapper#getSystemService(String)} of certain 901 * {@link ContextWrapper} subclasses we found in the wild. 902 * 903 * @param name The name of the desired service. 904 * @return The service or {@link null} if the name does not exist. 905 */ 906 @Override getSystemService(String name)907 public Object getSystemService(String name) { 908 return mOriginalContext.getSystemService(name); 909 } 910 } 911 912 @Override onCreate(Bundle icicle)913 protected void onCreate(Bundle icicle) { 914 super.onCreate(icicle); 915 mEditText = new EditText(new Bug118341760ContextWrapper(this)); 916 // Use SystemClock.elapsedRealtimeNanos()) as a unique ID of this edit text. 917 mEditText.setPrivateImeOptions(Long.toString(SystemClock.elapsedRealtimeNanos())); 918 final LinearLayout layout = new LinearLayout(this); 919 layout.setOrientation(LinearLayout.VERTICAL); 920 layout.addView(mEditText); 921 mEditText.requestFocus(); 922 setContentView(layout); 923 } 924 getEditText()925 EditText getEditText() { 926 return mEditText; 927 } 928 } 929 assertImeWindowAndDisplayConfiguration( WindowState imeWinState, DisplayContent display)930 private void assertImeWindowAndDisplayConfiguration( 931 WindowState imeWinState, DisplayContent display) { 932 // The IME window should inherit the configuration from the IME DisplayArea. 933 final WindowManagerState.DisplayArea imeContainerDisplayArea = display.getImeContainer(); 934 final Configuration configurationForIme = imeWinState.mMergedOverrideConfiguration; 935 final Configuration configurationForImeContainer = 936 imeContainerDisplayArea.mMergedOverrideConfiguration; 937 final int displayDensityDpiForIme = configurationForIme.densityDpi; 938 final int displayDensityDpiForImeContainer = configurationForImeContainer.densityDpi; 939 final Rect displayBoundsForIme = configurationForIme.windowConfiguration.getBounds(); 940 final Rect displayBoundsForImeContainer = 941 configurationForImeContainer.windowConfiguration.getBounds(); 942 943 assertEquals("Display density not the same", 944 displayDensityDpiForImeContainer, displayDensityDpiForIme); 945 assertEquals("Display bounds not the same", 946 displayBoundsForImeContainer, displayBoundsForIme); 947 } 948 tapAndAssertEditorFocusedOnImeActivity( TestActivitySession<? extends ImeTestActivity> activitySession, int expectDisplayId)949 private void tapAndAssertEditorFocusedOnImeActivity( 950 TestActivitySession<? extends ImeTestActivity> activitySession, int expectDisplayId) { 951 final int[] location = new int[2]; 952 waitAndAssertActivityStateOnDisplay(activitySession.getActivity().getComponentName(), 953 STATE_RESUMED, expectDisplayId, 954 "ImeActivity failed to appear on display#" + expectDisplayId); 955 activitySession.runOnMainSyncAndWait(() -> { 956 final EditText editText = activitySession.getActivity().mEditText; 957 editText.getLocationOnScreen(location); 958 }); 959 final ComponentName expectComponent = activitySession.getActivity().getComponentName(); 960 tapOnDisplaySync(location[0], location[1], expectDisplayId); 961 mWmState.computeState(activitySession.getActivity().getComponentName()); 962 mWmState.assertFocusedAppOnDisplay("Activity not focus on the display", expectComponent, 963 expectDisplayId); 964 } 965 showSoftInputAndAssertImeShownOnDisplay(int displayId, TestActivitySession<? extends ImeTestActivity> activitySession, ImeEventStream stream)966 private void showSoftInputAndAssertImeShownOnDisplay(int displayId, 967 TestActivitySession<? extends ImeTestActivity> activitySession, ImeEventStream stream) 968 throws Exception { 969 activitySession.runOnMainSyncAndWait( 970 activitySession.getActivity()::showSoftInput); 971 expectEvent(stream, editorMatcher("onStartInputView", 972 activitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 973 // Assert the IME is shown on the expected display. 974 mWmState.waitAndAssertImeWindowShownOnDisplay(displayId); 975 } 976 } 977