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