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