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