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