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