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.MockImeHelper.createManagedMockImeSession; 22 import static android.server.wm.UiDeviceUtils.pressSleepButton; 23 import static android.server.wm.UiDeviceUtils.pressUnlockButton; 24 import static android.server.wm.UiDeviceUtils.pressWakeupButton; 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.notExpectEvent; 44 45 import static com.google.common.truth.Truth.assertThat; 46 import static com.google.common.truth.Truth.assertWithMessage; 47 48 import static org.junit.Assert.assertEquals; 49 import static org.junit.Assert.assertFalse; 50 import static org.junit.Assert.assertTrue; 51 import static org.junit.Assume.assumeFalse; 52 import static org.junit.Assume.assumeTrue; 53 54 import android.app.Activity; 55 import android.app.WallpaperManager; 56 import android.content.ComponentName; 57 import android.content.Context; 58 import android.content.ContextWrapper; 59 import android.content.Intent; 60 import android.content.res.Configuration; 61 import android.content.res.Resources; 62 import android.graphics.Bitmap; 63 import android.graphics.Canvas; 64 import android.graphics.Color; 65 import android.graphics.Rect; 66 import android.os.Bundle; 67 import android.os.SystemClock; 68 import android.platform.test.annotations.Presubmit; 69 import android.server.wm.TestJournalProvider.TestJournalContainer; 70 import android.server.wm.WindowManagerState.DisplayContent; 71 import android.server.wm.WindowManagerState.WindowState; 72 import android.server.wm.intent.Activities; 73 import android.text.TextUtils; 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 final ChangeWallpaperSession wallpaperSession = createManagedChangeWallpaperSession(); 149 150 final DisplayContent untrustedDisplay = createManagedExternalDisplaySession() 151 .setPublicDisplay(true).setShowSystemDecorations(true).createVirtualDisplay(); 152 153 final DisplayContent decoredSystemDisplay = createManagedVirtualDisplaySession() 154 .setSimulateDisplay(true).setShowSystemDecorations(true).createDisplay(); 155 156 final Bitmap tmpWallpaper = wallpaperSession.getTestBitmap(); 157 wallpaperSession.setImageWallpaper(tmpWallpaper); 158 159 assertTrue("Wallpaper must be displayed on system owned display with system decor flag", 160 mWmState.waitForWithAmState( 161 state -> isWallpaperOnDisplay(state, decoredSystemDisplay.mId), 162 "wallpaper window to show")); 163 164 assertFalse("Wallpaper must not be displayed on the untrusted display", 165 isWallpaperOnDisplay(mWmState, untrustedDisplay.mId)); 166 } 167 createManagedChangeWallpaperSession()168 private ChangeWallpaperSession createManagedChangeWallpaperSession() { 169 return mObjectTracker.manage(new ChangeWallpaperSession()); 170 } 171 172 private class ChangeWallpaperSession implements AutoCloseable { 173 private final WallpaperManager mWallpaperManager; 174 private Bitmap mTestBitmap; 175 ChangeWallpaperSession()176 public ChangeWallpaperSession() { 177 mWallpaperManager = WallpaperManager.getInstance(mContext); 178 } 179 getTestBitmap()180 public Bitmap getTestBitmap() { 181 if (mTestBitmap == null) { 182 mTestBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); 183 final Canvas canvas = new Canvas(mTestBitmap); 184 canvas.drawColor(Color.BLUE); 185 } 186 return mTestBitmap; 187 } 188 setImageWallpaper(Bitmap bitmap)189 public void setImageWallpaper(Bitmap bitmap) { 190 SystemUtil.runWithShellPermissionIdentity(() -> 191 mWallpaperManager.setBitmap(bitmap)); 192 } 193 setWallpaperComponent(ComponentName componentName)194 public void setWallpaperComponent(ComponentName componentName) { 195 SystemUtil.runWithShellPermissionIdentity(() -> 196 mWallpaperManager.setWallpaperComponent(componentName)); 197 } 198 199 @Override close()200 public void close() { 201 SystemUtil.runWithShellPermissionIdentity(() -> mWallpaperManager.clearWallpaper()); 202 if (mTestBitmap != null) { 203 mTestBitmap.recycle(); 204 } 205 // Turning screen off/on to flush deferred color events due to wallpaper changed. 206 pressSleepButton(); 207 pressWakeupButton(); 208 pressUnlockButton(); 209 } 210 } 211 isWallpaperOnDisplay(WindowManagerState windowManagerState, int displayId)212 private boolean isWallpaperOnDisplay(WindowManagerState windowManagerState, int displayId) { 213 return windowManagerState.getMatchingWindowType(TYPE_WALLPAPER).stream().anyMatch( 214 w -> w.getDisplayId() == displayId); 215 } 216 217 // Navigation bar related tests 218 // TODO(115978725): add runtime sys decor change test once we can do this. 219 /** 220 * Test that navigation bar should show on display with system decoration. 221 */ 222 @Test testNavBarShowingOnDisplayWithDecor()223 public void testNavBarShowingOnDisplayWithDecor() { 224 assumeHasBars(); 225 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 226 .setSimulateDisplay(true).setShowSystemDecorations(true).createDisplay(); 227 228 mWmState.waitAndAssertNavBarShownOnDisplay(newDisplay.mId); 229 } 230 231 /** 232 * Test that navigation bar should not show on display without system decoration. 233 */ 234 @Test testNavBarNotShowingOnDisplayWithoutDecor()235 public void testNavBarNotShowingOnDisplayWithoutDecor() { 236 assumeHasBars(); 237 // Wait for system decoration showing and record current nav states. 238 mWmState.waitForHomeActivityVisible(); 239 final List<WindowState> expected = mWmState.getAllNavigationBarStates(); 240 241 createManagedVirtualDisplaySession().setSimulateDisplay(true) 242 .setShowSystemDecorations(false).createDisplay(); 243 244 waitAndAssertNavBarStatesAreTheSame(expected); 245 } 246 247 /** 248 * Test that navigation bar should not show on private display even if the display 249 * supports system decoration. 250 */ 251 @Test testNavBarNotShowingOnPrivateDisplay()252 public void testNavBarNotShowingOnPrivateDisplay() { 253 assumeHasBars(); 254 // Wait for system decoration showing and record current nav states. 255 mWmState.waitForHomeActivityVisible(); 256 final List<WindowState> expected = mWmState.getAllNavigationBarStates(); 257 258 createManagedExternalDisplaySession().setPublicDisplay(false) 259 .setShowSystemDecorations(true).createVirtualDisplay(); 260 261 waitAndAssertNavBarStatesAreTheSame(expected); 262 } 263 waitAndAssertNavBarStatesAreTheSame(List<WindowState> expected)264 private void waitAndAssertNavBarStatesAreTheSame(List<WindowState> expected) { 265 // This is used to verify that we have nav bars shown on the same displays 266 // as before the test. 267 // 268 // The strategy is: 269 // Once a display with system ui decor support is created and a nav bar shows on the 270 // display, go back to verify whether the nav bar states are unchanged to verify that no nav 271 // bars were added to a display that was added before executing this method that shouldn't 272 // have nav bars (i.e. private or without system ui decor). 273 try (final VirtualDisplaySession secondDisplaySession = new VirtualDisplaySession()) { 274 final DisplayContent supportsSysDecorDisplay = secondDisplaySession 275 .setSimulateDisplay(true).setShowSystemDecorations(true).createDisplay(); 276 mWmState.waitAndAssertNavBarShownOnDisplay(supportsSysDecorDisplay.mId); 277 // This display has finished his task. Just close it. 278 } 279 280 mWmState.computeState(); 281 final List<WindowState> result = mWmState.getAllNavigationBarStates(); 282 283 assertEquals("The number of nav bars should be the same", expected.size(), result.size()); 284 285 mWmState.getDisplays().forEach(displayContent -> { 286 List<WindowState> navWindows = expected.stream().filter(ws -> 287 ws.getDisplayId() == displayContent.mId) 288 .collect(Collectors.toList()); 289 290 mWmState.waitAndAssertNavBarShownOnDisplay(displayContent.mId, navWindows.size()); 291 }); 292 } 293 294 // Secondary Home related tests 295 /** 296 * Tests launching a home activity on virtual display without system decoration support. 297 */ 298 @Test testLaunchHomeActivityOnSecondaryDisplayWithoutDecorations()299 public void testLaunchHomeActivityOnSecondaryDisplayWithoutDecorations() { 300 createManagedHomeActivitySession(SECONDARY_HOME_ACTIVITY); 301 302 // Create new virtual display without system decoration support. 303 final DisplayContent newDisplay = createManagedExternalDisplaySession() 304 .createVirtualDisplay(); 305 306 // Secondary home activity can't be launched on the display without system decoration 307 // support. 308 assertEquals("No stacks on newly launched virtual display", 0, newDisplay.mRootTasks.size()); 309 } 310 311 /** Tests launching a home activity on untrusted virtual display. */ 312 @Test testLaunchHomeActivityOnUntrustedVirtualSecondaryDisplay()313 public void testLaunchHomeActivityOnUntrustedVirtualSecondaryDisplay() { 314 createManagedHomeActivitySession(SECONDARY_HOME_ACTIVITY); 315 316 // Create new virtual display with system decoration support flag. 317 final DisplayContent newDisplay = createManagedExternalDisplaySession() 318 .setPublicDisplay(true).setShowSystemDecorations(true).createVirtualDisplay(); 319 320 // Secondary home activity can't be launched on the untrusted virtual display. 321 assertEquals("No stacks on untrusted virtual display", 0, newDisplay.mRootTasks.size()); 322 } 323 324 /** 325 * Tests launching a single instance home activity on virtual display with system decoration 326 * support. 327 */ 328 @Test testLaunchSingleHomeActivityOnDisplayWithDecorations()329 public void testLaunchSingleHomeActivityOnDisplayWithDecorations() { 330 createManagedHomeActivitySession(SINGLE_HOME_ACTIVITY); 331 332 // If default home doesn't support multi-instance, default secondary home activity 333 // should be automatically launched on the new display. 334 assertSecondaryHomeResumedOnNewDisplay(getDefaultSecondaryHomeComponent()); 335 } 336 337 /** 338 * Tests launching a single instance home activity with SECONDARY_HOME on virtual display with 339 * system decoration support. 340 */ 341 @Test testLaunchSingleSecondaryHomeActivityOnDisplayWithDecorations()342 public void testLaunchSingleSecondaryHomeActivityOnDisplayWithDecorations() { 343 createManagedHomeActivitySession(SINGLE_SECONDARY_HOME_ACTIVITY); 344 345 // If provided secondary home doesn't support multi-instance, default secondary home 346 // activity should be automatically launched on the new display. 347 assertSecondaryHomeResumedOnNewDisplay(getDefaultSecondaryHomeComponent()); 348 } 349 350 /** 351 * Tests launching a multi-instance home activity on virtual display with system decoration 352 * support. 353 */ 354 @Test testLaunchHomeActivityOnDisplayWithDecorations()355 public void testLaunchHomeActivityOnDisplayWithDecorations() { 356 createManagedHomeActivitySession(HOME_ACTIVITY); 357 358 // If default home doesn't have SECONDARY_HOME category, default secondary home 359 // activity should be automatically launched on the new display. 360 assertSecondaryHomeResumedOnNewDisplay(getDefaultSecondaryHomeComponent()); 361 } 362 363 /** 364 * Tests launching a multi-instance home activity with SECONDARY_HOME on virtual display with 365 * system decoration support. 366 */ 367 @Test testLaunchSecondaryHomeActivityOnDisplayWithDecorations()368 public void testLaunchSecondaryHomeActivityOnDisplayWithDecorations() { 369 createManagedHomeActivitySession(SECONDARY_HOME_ACTIVITY); 370 boolean useSystemProvidedLauncher = mContext.getResources().getBoolean( 371 Resources.getSystem().getIdentifier("config_useSystemProvidedLauncherForSecondary", 372 "bool", "android")); 373 374 if (useSystemProvidedLauncher) { 375 // Default secondary home activity should be automatically launched on the new display 376 // if forced by the config. 377 assertSecondaryHomeResumedOnNewDisplay(getDefaultSecondaryHomeComponent()); 378 } else { 379 // Provided secondary home activity should be automatically launched on the new display. 380 assertSecondaryHomeResumedOnNewDisplay(SECONDARY_HOME_ACTIVITY); 381 } 382 } 383 assertSecondaryHomeResumedOnNewDisplay(ComponentName homeComponentName)384 private void assertSecondaryHomeResumedOnNewDisplay(ComponentName homeComponentName) { 385 // Create new simulated display with system decoration support. 386 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 387 .setSimulateDisplay(true) 388 .setShowSystemDecorations(true) 389 .createDisplay(); 390 391 waitAndAssertActivityStateOnDisplay(homeComponentName, STATE_RESUMED, 392 newDisplay.mId, "Activity launched on secondary display must be resumed"); 393 394 tapOnDisplayCenter(newDisplay.mId); 395 assertEquals("Top activity must be home type", ACTIVITY_TYPE_HOME, 396 mWmState.getFrontRootTaskActivityType(newDisplay.mId)); 397 } 398 399 // IME related tests 400 @Test testImeWindowCanSwitchToDifferentDisplays()401 public void testImeWindowCanSwitchToDifferentDisplays() throws Exception { 402 assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme()); 403 404 final MockImeSession mockImeSession = createManagedMockImeSession(this); 405 final TestActivitySession<ImeTestActivity> imeTestActivitySession = 406 createManagedTestActivitySession(); 407 final TestActivitySession<ImeTestActivity2> imeTestActivitySession2 = 408 createManagedTestActivitySession(); 409 410 // Create a virtual display and launch an activity on it. 411 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 412 .setShowSystemDecorations(true) 413 .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL) 414 .setSimulateDisplay(true) 415 .createDisplay(); 416 417 final ImeEventStream stream = mockImeSession.openEventStream(); 418 419 imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class, 420 newDisplay.mId); 421 422 expectEvent(stream, editorMatcher("onStartInput", 423 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 424 425 // Make the activity to show soft input. 426 showSoftInputAndAssertImeShownOnDisplay(newDisplay.mId, imeTestActivitySession, stream); 427 428 // Assert the configuration of the IME window is the same as the configuration of the 429 // virtual display. 430 assertImeWindowAndDisplayConfiguration(mWmState.getImeWindowState(), newDisplay); 431 432 // Launch another activity on the default display. 433 imeTestActivitySession2.launchTestActivityOnDisplaySync( 434 ImeTestActivity2.class, DEFAULT_DISPLAY); 435 expectEvent(stream, editorMatcher("onStartInput", 436 imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 437 438 // Make the activity to show soft input. 439 showSoftInputAndAssertImeShownOnDisplay(DEFAULT_DISPLAY, imeTestActivitySession2, stream); 440 441 // Assert the configuration of the IME window is the same as the configuration of the 442 // default display. 443 assertImeWindowAndDisplayConfiguration(mWmState.getImeWindowState(), 444 mWmState.getDisplay(DEFAULT_DISPLAY)); 445 } 446 447 @Test testImeApiForBug118341760()448 public void testImeApiForBug118341760() throws Exception { 449 assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme()); 450 451 final long TIMEOUT_START_INPUT = TimeUnit.SECONDS.toMillis(5); 452 453 final MockImeSession mockImeSession = createManagedMockImeSession(this); 454 final TestActivitySession<ImeTestActivityWithBrokenContextWrapper> imeTestActivitySession = 455 createManagedTestActivitySession(); 456 // Create a virtual display and launch an activity on it. 457 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 458 .setShowSystemDecorations(true) 459 .setSimulateDisplay(true) 460 .createDisplay(); 461 imeTestActivitySession.launchTestActivityOnDisplaySync( 462 ImeTestActivityWithBrokenContextWrapper.class, newDisplay.mId); 463 464 final ImeTestActivityWithBrokenContextWrapper activity = 465 imeTestActivitySession.getActivity(); 466 final ImeEventStream stream = mockImeSession.openEventStream(); 467 final String privateImeOption = activity.getEditText().getPrivateImeOptions(); 468 expectEvent(stream, event -> { 469 if (!TextUtils.equals("onStartInput", event.getEventName())) { 470 return false; 471 } 472 final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo"); 473 return TextUtils.equals(editorInfo.packageName, mContext.getPackageName()) 474 && TextUtils.equals(editorInfo.privateImeOptions, privateImeOption); 475 }, TIMEOUT_START_INPUT); 476 477 imeTestActivitySession.runOnMainSyncAndWait(() -> { 478 final InputMethodManager imm = activity.getSystemService(InputMethodManager.class); 479 assertTrue("InputMethodManager.isActive() should work", 480 imm.isActive(activity.getEditText())); 481 }); 482 } 483 484 @Test testImeWindowCanSwitchWhenTopFocusedDisplayChange()485 public void testImeWindowCanSwitchWhenTopFocusedDisplayChange() throws Exception { 486 // If config_perDisplayFocusEnabled, the focus will not move even if touching on 487 // the Activity in the different display. 488 assumeFalse(perDisplayFocusEnabled()); 489 assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme()); 490 491 final MockImeSession mockImeSession = createManagedMockImeSession(this); 492 final TestActivitySession<ImeTestActivity> imeTestActivitySession = 493 createManagedTestActivitySession(); 494 final TestActivitySession<ImeTestActivity2> imeTestActivitySession2 = 495 createManagedTestActivitySession(); 496 497 // Create a virtual display and launch an activity on virtual & default display. 498 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 499 .setShowSystemDecorations(true) 500 .setSimulateDisplay(true) 501 .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL) 502 .createDisplay(); 503 imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class, 504 DEFAULT_DISPLAY); 505 imeTestActivitySession2.launchTestActivityOnDisplaySync(ImeTestActivity2.class, 506 newDisplay.mId); 507 508 final DisplayContent defDisplay = mWmState.getDisplay(DEFAULT_DISPLAY); 509 final ImeEventStream stream = mockImeSession.openEventStream(); 510 511 // Tap on the imeTestActivity task center instead of the display center because 512 // the activity might not be spanning the entire display 513 WindowManagerState.Task imeTestActivityTask = mWmState 514 .getTaskByActivity(imeTestActivitySession.getActivity().getComponentName()); 515 tapOnTaskCenter(imeTestActivityTask); 516 expectEvent(stream, editorMatcher("onStartInput", 517 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 518 showSoftInputAndAssertImeShownOnDisplay(defDisplay.mId, imeTestActivitySession, stream); 519 520 // Tap virtual display as top focused display & request focus on EditText to show 521 // soft input. 522 tapOnDisplayCenter(newDisplay.mId); 523 expectEvent(stream, editorMatcher("onStartInput", 524 imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 525 showSoftInputAndAssertImeShownOnDisplay(newDisplay.mId, imeTestActivitySession2, stream); 526 527 // Tap on the imeTestActivity task center instead of the display center because 528 // the activity might not be spanning the entire display 529 imeTestActivityTask = mWmState 530 .getTaskByActivity(imeTestActivitySession.getActivity().getComponentName()); 531 tapOnTaskCenter(imeTestActivityTask); 532 expectEvent(stream, editorMatcher("onStartInput", 533 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 534 showSoftInputAndAssertImeShownOnDisplay(defDisplay.mId, imeTestActivitySession, stream); 535 } 536 537 /** 538 * Test that the IME can be shown in a different display (actually the default display) than 539 * the display on which the target IME application is shown. Then test several basic operations 540 * in {@link InputConnection}. 541 */ 542 @Test testCrossDisplayBasicImeOperations()543 public void testCrossDisplayBasicImeOperations() throws Exception { 544 assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme()); 545 546 final MockImeSession mockImeSession = createManagedMockImeSession(this); 547 final TestActivitySession<ImeTestActivity> imeTestActivitySession = 548 createManagedTestActivitySession(); 549 550 // Create a virtual display by app and assume the display should not show IME window. 551 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 552 .setPublicDisplay(true) 553 .createDisplay(); 554 SystemUtil.runWithShellPermissionIdentity( 555 () -> assertTrue("Display should not support showing IME window", 556 mTargetContext.getSystemService(WindowManager.class) 557 .getDisplayImePolicy(newDisplay.mId) 558 == DISPLAY_IME_POLICY_FALLBACK_DISPLAY)); 559 560 // Launch Ime test activity in virtual display. 561 imeTestActivitySession.launchTestActivityOnDisplay(ImeTestActivity.class, 562 newDisplay.mId); 563 final ImeEventStream stream = mockImeSession.openEventStream(); 564 565 // Expect onStartInput would be executed when user tapping on the 566 // non-system created display intentionally. 567 tapAndAssertEditorFocusedOnImeActivity(imeTestActivitySession, newDisplay.mId); 568 expectEvent(stream, editorMatcher("onStartInput", 569 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT); 570 571 // Verify the activity to show soft input on the default display. 572 showSoftInputAndAssertImeShownOnDisplay(DEFAULT_DISPLAY, imeTestActivitySession, stream); 573 574 // Commit text & make sure the input texts should be delivered to focused EditText on 575 // virtual display. 576 final EditText editText = imeTestActivitySession.getActivity().mEditText; 577 final String commitText = "test commit"; 578 expectCommand(stream, mockImeSession.callCommitText(commitText, 1), TIMEOUT); 579 imeTestActivitySession.runOnMainAndAssertWithTimeout( 580 () -> TextUtils.equals(commitText, editText.getText()), TIMEOUT, 581 "The input text should be delivered"); 582 583 // Since the IME and the IME target app are running in different displays, 584 // InputConnection#requestCursorUpdates() is not supported and it should return false. 585 // See InputMethodServiceTest#testOnUpdateCursorAnchorInfo() for the normal scenario. 586 final ImeCommand callCursorUpdates = mockImeSession.callRequestCursorUpdates( 587 InputConnection.CURSOR_UPDATE_IMMEDIATE); 588 assertFalse(expectCommand(stream, callCursorUpdates, TIMEOUT).getReturnBooleanValue()); 589 } 590 591 /** 592 * Test that the IME can be hidden with the {@link WindowManager#DISPLAY_IME_POLICY_HIDE} flag. 593 */ 594 @Test testDisplayPolicyImeHideImeOperation()595 public void testDisplayPolicyImeHideImeOperation() throws Exception { 596 assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme()); 597 598 final MockImeSession mockImeSession = createManagedMockImeSession(this); 599 final TestActivitySession<ImeTestActivity> imeTestActivitySession = 600 createManagedTestActivitySession(); 601 602 // Create a virtual display and launch an activity on virtual display. 603 final DisplayContent newDisplay = createManagedVirtualDisplaySession() 604 .setShowSystemDecorations(true) 605 .setDisplayImePolicy(DISPLAY_IME_POLICY_HIDE) 606 .setSimulateDisplay(true) 607 .createDisplay(); 608 609 // Launch Ime test activity and initial the editor focus on virtual display. 610 imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class, 611 newDisplay.mId); 612 613 // Verify the activity is launched to the secondary display. 614 final ComponentName imeTestActivityName = 615 imeTestActivitySession.getActivity().getComponentName(); 616 assertThat(mWmState.hasActivityInDisplay(newDisplay.mId, imeTestActivityName)).isTrue(); 617 618 // Verify invoking showSoftInput will be ignored when the display with the HIDE policy. 619 final ImeEventStream stream = mockImeSession.openEventStream(); 620 imeTestActivitySession.runOnMainSyncAndWait( 621 imeTestActivitySession.getActivity()::showSoftInput); 622 notExpectEvent(stream, editorMatcher("showSoftInput", 623 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), 624 NOT_EXPECT_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