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