• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.ime;
18 
19 import static android.server.wm.InputMethodVisibilityVerifier.expectImeInvisible;
20 import static android.server.wm.InputMethodVisibilityVerifier.expectImeVisible;
21 import static android.server.wm.MockImeHelper.createManagedMockImeSession;
22 import static android.server.wm.UiDeviceUtils.pressBackButton;
23 import static android.server.wm.WindowManagerState.STATE_RESUMED;
24 import static android.view.Display.DEFAULT_DISPLAY;
25 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
26 import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
27 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
28 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
29 
30 import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
31 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
32 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
33 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEventWithKeyValue;
34 import static com.android.cts.mockime.ImeEventStreamTestUtils.hideSoftInputMatcher;
35 import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
36 
37 import static com.google.common.truth.Truth.assertThat;
38 import static com.google.common.truth.Truth.assertWithMessage;
39 
40 import static org.junit.Assert.assertEquals;
41 import static org.junit.Assert.assertFalse;
42 import static org.junit.Assert.assertTrue;
43 import static org.junit.Assume.assumeFalse;
44 import static org.junit.Assume.assumeTrue;
45 
46 import android.app.Activity;
47 import android.content.ComponentName;
48 import android.content.Context;
49 import android.content.ContextWrapper;
50 import android.content.Intent;
51 import android.content.res.Configuration;
52 import android.graphics.Rect;
53 import android.os.Bundle;
54 import android.os.SystemClock;
55 import android.platform.test.annotations.Presubmit;
56 import android.platform.test.annotations.RequiresFlagsDisabled;
57 import android.platform.test.flag.junit.CheckFlagsRule;
58 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
59 import android.server.wm.MultiDisplayTestBase;
60 import android.server.wm.WindowManagerState;
61 import android.server.wm.WindowManagerState.DisplayContent;
62 import android.server.wm.WindowManagerState.WindowState;
63 import android.server.wm.intent.Activities;
64 import android.text.TextUtils;
65 import android.view.View;
66 import android.view.Window;
67 import android.view.WindowManager;
68 import android.view.inputmethod.EditorInfo;
69 import android.view.inputmethod.InputConnection;
70 import android.view.inputmethod.InputMethodManager;
71 import android.widget.EditText;
72 import android.widget.LinearLayout;
73 
74 import com.android.compatibility.common.util.PollingCheck;
75 import com.android.compatibility.common.util.SystemUtil;
76 import com.android.cts.mockime.ImeCommand;
77 import com.android.cts.mockime.ImeEventStream;
78 import com.android.cts.mockime.MockImeSession;
79 
80 import org.junit.Before;
81 import org.junit.Rule;
82 import org.junit.Test;
83 
84 import java.util.List;
85 import java.util.concurrent.TimeUnit;
86 
87 /**
88  * Build/Install/Run:
89  *     atest CtsWindowManagerDeviceIme:MultiDisplayImeTests
90  */
91 @Presubmit
92 @android.server.wm.annotation.Group3
93 public class MultiDisplayImeTests extends MultiDisplayTestBase {
94 
95     @Rule
96     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
97 
98     static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
99     static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
100 
101     @Before
102     @Override
setUp()103     public void setUp() throws Exception {
104         super.setUp();
105 
106         assumeTrue(supportsMultiDisplay());
107         assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());
108     }
109 
110     // TODO(b/383228193): Remove this method once fallbackDisplayForSecondaryUserOnSecondaryDisplay
111     //     flag is promoted.
112     @Override
getMainDisplayId()113     protected int getMainDisplayId() {
114         if (!android.view.inputmethod.Flags.fallbackDisplayForSecondaryUserOnSecondaryDisplay()) {
115             return DEFAULT_DISPLAY;
116         }
117         return super.getMainDisplayId();
118     }
119 
120     @Test
testImeWindowCanSwitchToDifferentDisplays()121     public void testImeWindowCanSwitchToDifferentDisplays() throws Exception {
122         final MockImeSession mockImeSession = createManagedMockImeSession(this);
123         final TestActivitySession<ImeTestActivity> imeTestActivitySession =
124                 createManagedTestActivitySession();
125         final TestActivitySession<ImeTestActivity2> imeTestActivitySession2 =
126                 createManagedTestActivitySession();
127 
128         // Create a virtual display and launch an activity on it.
129         final DisplayContent newDisplay = createManagedVirtualDisplaySession()
130                 .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL)
131                 .setSimulateDisplay(true)
132                 .createDisplay();
133 
134         final ImeEventStream stream = mockImeSession.openEventStream();
135 
136         imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
137                 newDisplay.mId);
138 
139         expectEvent(stream, editorMatcher("onStartInput",
140                 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
141 
142         // Make the activity to show soft input.
143         showSoftInputAndAssertImeShownOnDisplay(newDisplay.mId, imeTestActivitySession, stream);
144 
145         // Assert the configuration of the IME window is the same as the configuration of the
146         // virtual display.
147         assertImeWindowAndDisplayConfiguration(mWmState.getImeWindowState(), newDisplay);
148 
149         // Launch another activity on the main display of the user. When the test runs on the
150         // current user, the display will be the default display.
151         imeTestActivitySession2.launchTestActivityOnDisplaySync(
152                 ImeTestActivity2.class, getMainDisplayId());
153         expectEvent(stream, editorMatcher("onStartInput",
154                 imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
155 
156         // Make the activity to show soft input.
157         showSoftInputAndAssertImeShownOnDisplay(getMainDisplayId(), imeTestActivitySession2,
158                 stream);
159 
160         // Assert the configuration of the IME window is the same as the configuration of the
161         // main display of the user.
162         assertImeWindowAndDisplayConfiguration(mWmState.getImeWindowState(),
163                 mWmState.getDisplay(getMainDisplayId()));
164     }
165 
166     /**
167      * This checks that calling showSoftInput on the incorrect display, requiring the fallback IMM,
168      * will not drop the statsToken tracking the show request.
169      */
170     @Test
171     @RequiresFlagsDisabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
testFallbackImmMaintainsParameters()172     public void testFallbackImmMaintainsParameters() throws Exception {
173         try (var mockImeSession = createManagedMockImeSession(this);
174                 TestActivitySession<ImeTestActivity> imeTestActivitySession =
175                         createManagedTestActivitySession();
176                 var displaySession = createManagedVirtualDisplaySession()) {
177             final var newDisplay = displaySession.setSimulateDisplay(true).createDisplay();
178 
179             imeTestActivitySession.launchTestActivityOnDisplaySync(
180                     ImeTestActivity.class, newDisplay.mId);
181             final var activity = imeTestActivitySession.getActivity();
182             final var stream = mockImeSession.openEventStream();
183 
184             expectEvent(stream, editorMatcher("onStartInput",
185                     activity.mEditText.getPrivateImeOptions()), TIMEOUT);
186 
187             imeTestActivitySession.runOnMainSyncAndWait(() -> {
188                 final var imm = activity.getApplicationContext()
189                         .getSystemService(InputMethodManager.class);
190                 imm.showSoftInput(activity.mEditText, 0 /* flags */);
191             });
192 
193             expectImeVisible(TIMEOUT);
194             PollingCheck.waitFor(() -> !mockImeSession.hasPendingImeVisibilityRequests(),
195                     "No pending requests should remain after the IME is visible");
196         }
197     }
198 
199     @Test
testImeApiForBug118341760()200     public void testImeApiForBug118341760() throws Exception {
201         final MockImeSession mockImeSession = createManagedMockImeSession(this);
202         final TestActivitySession<ImeTestActivityWithBrokenContextWrapper> imeTestActivitySession =
203                 createManagedTestActivitySession();
204         // Create a virtual display and launch an activity on it.
205         final DisplayContent newDisplay = createManagedVirtualDisplaySession()
206                 .setSimulateDisplay(true)
207                 .createDisplay();
208         imeTestActivitySession.launchTestActivityOnDisplaySync(
209                 ImeTestActivityWithBrokenContextWrapper.class, newDisplay.mId);
210 
211         final ImeTestActivityWithBrokenContextWrapper activity =
212                 imeTestActivitySession.getActivity();
213         final ImeEventStream stream = mockImeSession.openEventStream();
214         final String privateImeOption = activity.getEditText().getPrivateImeOptions();
215         expectEvent(stream, event -> {
216             if (!TextUtils.equals("onStartInput", event.getEventName())) {
217                 return false;
218             }
219             final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo");
220             return TextUtils.equals(editorInfo.packageName, mContext.getPackageName())
221                     && TextUtils.equals(editorInfo.privateImeOptions, privateImeOption);
222         }, TIMEOUT);
223 
224         imeTestActivitySession.runOnMainSyncAndWait(() -> {
225             final InputMethodManager imm = activity.getSystemService(InputMethodManager.class);
226             assertTrue("InputMethodManager.isActive() should work",
227                     imm.isActive(activity.getEditText()));
228         });
229     }
230 
231     @Test
testImeWindowCanSwitchWhenTopFocusedDisplayChange()232     public void testImeWindowCanSwitchWhenTopFocusedDisplayChange() throws Exception {
233         // If config_perDisplayFocusEnabled, the focus will not move even if touching on
234         // the Activity in the different display.
235         assumeFalse(perDisplayFocusEnabled());
236 
237         final MockImeSession mockImeSession = createManagedMockImeSession(this);
238         final TestActivitySession<ImeTestActivity> imeTestActivitySession =
239                 createManagedTestActivitySession();
240         final TestActivitySession<ImeTestActivity2> imeTestActivitySession2 =
241                 createManagedTestActivitySession();
242 
243         // Create a virtual display and launch an activity on virtual & default display.
244         final DisplayContent newDisplay = createManagedVirtualDisplaySession()
245                 .setSimulateDisplay(true)
246                 .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL)
247                 .createDisplay();
248         imeTestActivitySession.launchTestActivityOnDisplaySync(
249                 ImeTestActivity.class, getMainDisplayId());
250         imeTestActivitySession2.launchTestActivityOnDisplaySync(ImeTestActivity2.class,
251                 newDisplay.mId);
252 
253         final DisplayContent defDisplay = mWmState.getDisplay(getMainDisplayId());
254         final ImeEventStream stream = mockImeSession.openEventStream();
255 
256         // Tap on the imeTestActivity task center instead of the display center because
257         // the activity might not be spanning the entire display
258         WindowManagerState.Task imeTestActivityTask = mWmState
259                 .getTaskByActivity(imeTestActivitySession.getActivity().getComponentName());
260         tapOnTaskCenter(imeTestActivityTask);
261         expectEvent(stream, editorMatcher("onStartInput",
262                 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
263         showSoftInputAndAssertImeShownOnDisplay(defDisplay.mId, imeTestActivitySession, stream);
264 
265         // Tap virtual display as top focused display & request focus on EditText to show
266         // soft input.
267         touchAndCancelOnDisplayCenterSync(newDisplay.mId);
268         expectEvent(stream, editorMatcher("onStartInput",
269                 imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
270         showSoftInputAndAssertImeShownOnDisplay(newDisplay.mId, imeTestActivitySession2, stream);
271 
272         // Tap on the imeTestActivity task center instead of the display center because
273         // the activity might not be spanning the entire display
274         imeTestActivityTask = mWmState
275                 .getTaskByActivity(imeTestActivitySession.getActivity().getComponentName());
276         tapOnTaskCenter(imeTestActivityTask);
277         expectEvent(stream, editorMatcher("onStartInput",
278                 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
279         showSoftInputAndAssertImeShownOnDisplay(defDisplay.mId, imeTestActivitySession, stream);
280     }
281 
282     /**
283      * Test that the IME can be shown in a different display (actually the default display) than
284      * the display on which the target IME application is shown.  Then test several basic operations
285      * in {@link InputConnection}.
286      */
287     @Test
testCrossDisplayBasicImeOperations()288     public void testCrossDisplayBasicImeOperations() throws Exception {
289         final MockImeSession mockImeSession = createManagedMockImeSession(this);
290         final TestActivitySession<ImeTestActivity> imeTestActivitySession =
291                 createManagedTestActivitySession();
292 
293         // Create a virtual display by app and assume the display should not show IME window.
294         final DisplayContent newDisplay = createManagedVirtualDisplaySession()
295                 .setPublicDisplay(true)
296                 .createDisplay();
297         SystemUtil.runWithShellPermissionIdentity(
298                 () -> assertTrue("Display should not support showing IME window",
299                         mTargetContext.getSystemService(WindowManager.class)
300                                 .getDisplayImePolicy(newDisplay.mId)
301                                 == DISPLAY_IME_POLICY_FALLBACK_DISPLAY));
302 
303         // Launch Ime test activity in virtual display.
304         imeTestActivitySession.launchTestActivityOnDisplay(ImeTestActivity.class,
305                 newDisplay.mId);
306         final ImeEventStream stream = mockImeSession.openEventStream();
307 
308         // Expect onStartInput would be executed when user tapping on the
309         // non-system created display intentionally.
310         tapAndAssertEditorFocusedOnImeActivity(imeTestActivitySession, newDisplay.mId);
311         expectEvent(stream, editorMatcher("onStartInput",
312                 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
313 
314         // Verify the activity to show soft input on the default display.
315         showSoftInputAndAssertImeShownOnDisplay(getMainDisplayId(), imeTestActivitySession, stream);
316 
317         // Commit text & make sure the input texts should be delivered to focused EditText on
318         // virtual display.
319         final EditText editText = imeTestActivitySession.getActivity().mEditText;
320         final String commitText = "test commit";
321         expectCommand(stream, mockImeSession.callCommitText(commitText, 1), TIMEOUT);
322         imeTestActivitySession.runOnMainAndAssertWithTimeout(
323                 () -> TextUtils.equals(commitText, editText.getText()), TIMEOUT,
324                 "The input text should be delivered");
325 
326         // Since the IME and the IME target app are running in different displays,
327         // InputConnection#requestCursorUpdates() is not supported and it should return false.
328         // See InputMethodServiceTest#testOnUpdateCursorAnchorInfo() for the normal scenario.
329         final ImeCommand callCursorUpdates = mockImeSession.callRequestCursorUpdates(
330                 InputConnection.CURSOR_UPDATE_IMMEDIATE);
331         assertFalse(expectCommand(stream, callCursorUpdates, TIMEOUT).getReturnBooleanValue());
332     }
333 
334     /**
335      * Test that the IME can be hidden with the {@link WindowManager#DISPLAY_IME_POLICY_HIDE} flag.
336      */
337     @Test
testDisplayPolicyImeHideImeOperation()338     public void testDisplayPolicyImeHideImeOperation() throws Exception {
339         final MockImeSession mockImeSession = createManagedMockImeSession(this);
340         final TestActivitySession<ImeTestActivity> imeTestActivitySession =
341                 createManagedTestActivitySession();
342 
343         // Create a virtual display and launch an activity on virtual display.
344         final DisplayContent newDisplay = createManagedVirtualDisplaySession()
345                 .setDisplayImePolicy(DISPLAY_IME_POLICY_HIDE)
346                 .setSimulateDisplay(true)
347                 .createDisplay();
348 
349         // Launch Ime test activity and initial the editor focus on virtual display.
350         imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
351                 newDisplay.mId);
352 
353         // Verify the activity is launched to the secondary display.
354         final ComponentName imeTestActivityName =
355                 imeTestActivitySession.getActivity().getComponentName();
356         assertThat(mWmState.hasActivityInDisplay(newDisplay.mId, imeTestActivityName)).isTrue();
357 
358         // Verify invoking showSoftInput will be ignored when the display with the HIDE policy.
359         final ImeEventStream stream = mockImeSession.openEventStream();
360         imeTestActivitySession.runOnMainSyncAndWait(
361                 imeTestActivitySession.getActivity()::showSoftInput);
362         notExpectEvent(stream, editorMatcher("showSoftInput",
363                 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
364                 NOT_EXPECT_TIMEOUT);
365     }
366 
367     /**
368      * A regression test for Bug 273630528.
369      *
370      * Test that the IME on the editor activity with embedded in virtual display will be hidden
371      * after pressing the back key.
372      */
373     @Test
testHideImeWhenImeTargetOnEmbeddedVirtualDisplay()374     public void testHideImeWhenImeTargetOnEmbeddedVirtualDisplay() throws Exception {
375         final VirtualDisplaySession session = createManagedVirtualDisplaySession();
376         final MockImeSession imeSession = createManagedMockImeSession(this);
377         final TestActivitySession<ImeTestActivity> imeActivitySession =
378                 createManagedTestActivitySession();
379 
380         // Setup a virtual display embedded on an activity.
381         final DisplayContent dc = session
382                 .setPublicDisplay(true)
383                 .setSupportsTouch(true)
384                 .createDisplay();
385 
386         // Launch a test activity on that virtual display and show IME by tapping the editor.
387         imeActivitySession.launchTestActivityOnDisplay(ImeTestActivity.class, dc.mId);
388         tapAndAssertEditorFocusedOnImeActivity(imeActivitySession, dc.mId);
389         final ImeEventStream stream = imeSession.openEventStream();
390         final String marker = imeActivitySession.getActivity().mEditText.getPrivateImeOptions();
391         expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
392 
393         // Expect soft-keyboard becomes visible after requesting show IME.
394         showSoftInputAndAssertImeShownOnDisplay(getMainDisplayId(), imeActivitySession, stream);
395         expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
396                 View.VISIBLE, TIMEOUT);
397         expectImeVisible(TIMEOUT);
398 
399         // Pressing back key, expect soft-keyboard will become invisible.
400         pressBackButton();
401         expectEvent(stream, hideSoftInputMatcher(), TIMEOUT);
402         expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
403                 View.GONE, TIMEOUT);
404         expectImeInvisible(TIMEOUT);
405     }
406 
407     @Test
testImeWindowCanShownWhenActivityMovedToDisplay()408     public void testImeWindowCanShownWhenActivityMovedToDisplay() throws Exception {
409         // If config_perDisplayFocusEnabled, the focus will not move even if touching on
410         // the Activity in the different display.
411         assumeFalse(perDisplayFocusEnabled());
412 
413         // Launch a regular activity on default display at the test beginning to prevent the test
414         // may mis-touch the launcher icon that breaks the test expectation.
415         final TestActivitySession<Activities.RegularActivity> testActivitySession =
416                 createManagedTestActivitySession();
417         testActivitySession.launchTestActivityOnDisplaySync(
418                 Activities.RegularActivity.class, getMainDisplayId());
419 
420         // Create a virtual display and launch an activity on virtual display.
421         final DisplayContent newDisplay = createManagedVirtualDisplaySession()
422                 .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL)
423                 .setSimulateDisplay(true)
424                 .createDisplay();
425 
426         // Leverage MockImeSession to ensure at least an IME exists as default.
427         final MockImeSession mockImeSession = createManagedMockImeSession(this);
428         final TestActivitySession<ImeTestActivity> imeTestActivitySession =
429                 createManagedTestActivitySession();
430         // Launch Ime test activity and initial the editor focus on virtual display.
431         imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
432                 newDisplay.mId);
433 
434         // Verify the activity is launched to the secondary display.
435         final ComponentName imeTestActivityName =
436                 imeTestActivitySession.getActivity().getComponentName();
437         assertThat(mWmState.hasActivityInDisplay(newDisplay.mId, imeTestActivityName)).isTrue();
438 
439         // Tap default display, assume a pointer-out-side event will happened to change the top
440         // display.
441         final DisplayContent defDisplay = mWmState.getDisplay(getMainDisplayId());
442         tapOnDisplayCenter(defDisplay.mId);
443         mWmState.waitForAppTransitionIdleOnDisplay(getMainDisplayId());
444         mWmState.assertValidity();
445 
446         // Reparent ImeTestActivity from virtual display to default display.
447         getLaunchActivityBuilder()
448                 .setUseInstrumentation()
449                 .setTargetActivity(imeTestActivitySession.getActivity().getComponentName())
450                 .setIntentFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
451                 .allowMultipleInstances(false)
452                 .setDisplayId(getMainDisplayId())
453                 .execute();
454         waitAndAssertResumedAndFocusedActivityOnDisplay(
455                 imeTestActivitySession.getActivity().getComponentName(),
456                 getMainDisplayId(),
457                 "Activity launched on default display and on top");
458 
459         // Activity is no longer on the secondary display
460         assertThat(mWmState.hasActivityInDisplay(newDisplay.mId, imeTestActivityName)).isFalse();
461 
462         // Tap on the imeTestActivity task center instead of the display center because
463         // the activity might not be spanning the entire display
464         final ImeEventStream stream = mockImeSession.openEventStream();
465         final WindowManagerState.Task testActivityTask = mWmState
466                 .getTaskByActivity(imeTestActivitySession.getActivity().getComponentName());
467         tapOnTaskCenter(testActivityTask);
468         expectEvent(stream, editorMatcher("onStartInput",
469                 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
470 
471         // Verify the activity shows soft input on the default display.
472         showSoftInputAndAssertImeShownOnDisplay(getMainDisplayId(), imeTestActivitySession, stream);
473     }
474 
475     @Test
testNoConfigurationChangedWhenSwitchBetweenTwoIdenticalDisplays()476     public void testNoConfigurationChangedWhenSwitchBetweenTwoIdenticalDisplays() throws Exception {
477         // If config_perDisplayFocusEnabled, the focus will not move even if touching on
478         // the Activity in the different display.
479         assumeFalse(perDisplayFocusEnabled());
480 
481         // Create two displays with the same display metrics
482         final List<DisplayContent> newDisplays = createManagedVirtualDisplaySession()
483                 .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL)
484                 .setOwnContentOnly(true)
485                 .setSimulateDisplay(true)
486                 .setResizeDisplay(false)
487                 .createDisplays(2);
488         final DisplayContent firstDisplay = newDisplays.get(0);
489         final DisplayContent secondDisplay = newDisplays.get(1);
490 
491         // Skip if the test environment somehow didn't create 2 displays with identical size.
492         assumeTrue("Skip the test if the size of the created displays aren't identical",
493                 firstDisplay.getDisplayRect().equals(secondDisplay.getDisplayRect()));
494 
495         final TestActivitySession<ImeTestActivity2> imeTestActivitySession2 =
496                 createManagedTestActivitySession();
497         imeTestActivitySession2.launchTestActivityOnDisplaySync(
498                 ImeTestActivity2.class, secondDisplay.mId);
499 
500         // Make firstDisplay the top focus display.
501         touchAndCancelOnDisplayCenterSync(firstDisplay.mId);
502 
503         mWmState.waitForWithAmState(state -> state.getFocusedDisplayId() == firstDisplay.mId,
504                 "First display must be top focused.");
505 
506         // Initialize IME test environment
507         final MockImeSession mockImeSession = createManagedMockImeSession(this);
508         final TestActivitySession<ImeTestActivity> imeTestActivitySession =
509                 createManagedTestActivitySession();
510         ImeEventStream stream = mockImeSession.openEventStream();
511         // Filter out onConfigurationChanged events in case that IME is moved from the default
512         // display to the firstDisplay.
513         ImeEventStream configChangeVerifyStream = clearOnConfigurationChangedFromStream(stream);
514 
515         imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
516                 firstDisplay.mId);
517         // Wait until IME is ready for the IME client to call showSoftInput().
518         expectEvent(stream, editorMatcher("onStartInput",
519                 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
520 
521         int imeDisplayId = expectCommand(stream, mockImeSession.callGetDisplayId(),
522                 TIMEOUT).getReturnIntegerValue();
523         assertThat(imeDisplayId).isEqualTo(firstDisplay.mId);
524 
525         imeTestActivitySession.runOnMainSyncAndWait(
526                 imeTestActivitySession.getActivity()::showSoftInput);
527         waitOrderedImeEventsThenAssertImeShown(stream, firstDisplay.mId,
528                 event -> "showSoftInput".equals(event.getEventName()));
529         try {
530             // Launch Ime must not lead to screen size changes.
531             waitAndAssertImeNoScreenSizeChanged(configChangeVerifyStream);
532 
533             final Rect currentBoundsOnFirstDisplay = expectCommand(stream,
534                     mockImeSession.callGetCurrentWindowMetricsBounds(), TIMEOUT)
535                     .getReturnParcelableValue();
536 
537             // Clear onConfigurationChanged events before IME moves to the secondary display to
538             // prevent flaky because IME may receive configuration updates which we don't care
539             // about. An example is CONFIG_KEYBOARD_HIDDEN.
540             configChangeVerifyStream = clearOnConfigurationChangedFromStream(stream);
541 
542             // Tap secondDisplay to change it to the top focused display.
543             touchAndCancelOnDisplayCenterSync(firstDisplay.mId);
544 
545             // Move ImeTestActivity from firstDisplay to secondDisplay.
546             getLaunchActivityBuilder()
547                     .setUseInstrumentation()
548                     .setTargetActivity(imeTestActivitySession.getActivity().getComponentName())
549                     .setIntentFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
550                     .allowMultipleInstances(false)
551                     .setDisplayId(secondDisplay.mId).execute();
552 
553             // Make sure ImeTestActivity is move from the firstDisplay to the secondDisplay
554             waitAndAssertResumedAndFocusedActivityOnDisplay(
555                     imeTestActivitySession.getActivity().getComponentName(), secondDisplay.mId,
556                     "ImeTestActivity must be top-resumed on display#" + secondDisplay.mId);
557             assertThat(mWmState.hasActivityInDisplay(firstDisplay.mId,
558                     imeTestActivitySession.getActivity().getComponentName())).isFalse();
559             // Wait until IME is ready for the IME client to call showSoftInput().
560             expectEvent(stream, editorMatcher("onStartInput",
561                     imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
562                     TIMEOUT);
563             imeDisplayId = expectCommand(stream, mockImeSession.callGetDisplayId(),
564                     TIMEOUT).getReturnIntegerValue();
565             assertThat(imeDisplayId).isEqualTo(secondDisplay.mId);
566 
567             // With the refactor, the additional show is not needed, as we already verified that
568             // the IME is showing
569             if (!android.view.inputmethod.Flags.refactorInsetsController()) {
570                 // Show soft input again to trigger IME movement.
571                 imeTestActivitySession.runOnMainSyncAndWait(
572                         imeTestActivitySession.getActivity()::showSoftInput);
573                 waitOrderedImeEventsThenAssertImeShown(stream, secondDisplay.mId,
574                         event -> "showSoftInput".equals(event.getEventName()));
575             }
576 
577             // Moving IME to the display with the same display metrics must not lead to
578             // screen size changes.
579             waitAndAssertImeNoScreenSizeChanged(configChangeVerifyStream);
580 
581             final Rect currentBoundsOnSecondDisplay = expectCommand(stream,
582                     mockImeSession.callGetCurrentWindowMetricsBounds(), TIMEOUT)
583                     .getReturnParcelableValue();
584 
585             assertWithMessage("The current WindowMetrics bounds of IME must not be changed.")
586                     .that(currentBoundsOnFirstDisplay).isEqualTo(currentBoundsOnSecondDisplay);
587         } catch (AssertionError e) {
588             mWmState.computeState();
589             final Rect displayRect1 = mWmState.getDisplay(firstDisplay.mId).getDisplayRect();
590             final Rect displayRect2 = mWmState.getDisplay(secondDisplay.mId).getDisplayRect();
591             assumeTrue("Skip test since the size of one or both displays happens unexpected change",
592                     displayRect1.equals(displayRect2));
593             throw e;
594         }
595     }
596 
597     public static class ImeTestActivity extends Activity {
598         EditText mEditText;
599 
600         @Override
onCreate(Bundle icicle)601         protected void onCreate(Bundle icicle) {
602             super.onCreate(icicle);
603             mEditText = new EditText(this);
604             // Set private IME option for editorMatcher to identify which TextView received
605             // onStartInput event.
606             resetPrivateImeOptionsIdentifier();
607             final LinearLayout layout = new LinearLayout(this);
608             layout.setOrientation(LinearLayout.VERTICAL);
609             layout.addView(mEditText);
610             mEditText.requestFocus();
611             // SOFT_INPUT_STATE_UNSPECIFIED may produced unexpected behavior for CTS. To make tests
612             // deterministic, using SOFT_INPUT_STATE_UNCHANGED instead.
613             setUnchangedSoftInputState();
614             setContentView(layout);
615         }
616 
showSoftInput()617         void showSoftInput() {
618             final InputMethodManager imm = getSystemService(InputMethodManager.class);
619             imm.showSoftInput(mEditText, 0);
620         }
621 
resetPrivateImeOptionsIdentifier()622         void resetPrivateImeOptionsIdentifier() {
623             mEditText.setPrivateImeOptions(
624                     getClass().getName() + "/" + Long.toString(SystemClock.elapsedRealtimeNanos()));
625         }
626 
setUnchangedSoftInputState()627         private void setUnchangedSoftInputState() {
628             final Window window = getWindow();
629             final int currentSoftInputMode = window.getAttributes().softInputMode;
630             final int newSoftInputMode =
631                     (currentSoftInputMode & ~WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE)
632                             | SOFT_INPUT_STATE_UNCHANGED;
633             window.setSoftInputMode(newSoftInputMode);
634         }
635     }
636 
637     public static class ImeTestActivity2 extends ImeTestActivity {}
638 
639     public static final class ImeTestActivityWithBrokenContextWrapper extends Activity {
640         private EditText mEditText;
641 
642         /**
643          * Emulates the behavior of certain {@link ContextWrapper} subclasses we found in the wild.
644          *
645          * <p> Certain {@link ContextWrapper} subclass in the wild delegate method calls to
646          * ApplicationContext except for {@link #getSystemService(String)}.</p>
647          **/
648         private static final class Bug118341760ContextWrapper extends ContextWrapper {
649             private final Context mOriginalContext;
650 
Bug118341760ContextWrapper(Context base)651             Bug118341760ContextWrapper(Context base) {
652                 super(base.getApplicationContext());
653                 mOriginalContext = base;
654             }
655 
656             /**
657              * Emulates the behavior of {@link ContextWrapper#getSystemService(String)} of certain
658              * {@link ContextWrapper} subclasses we found in the wild.
659              *
660              * @param name The name of the desired service.
661              * @return The service or {@code null} if the name does not exist.
662              */
663             @Override
getSystemService(String name)664             public Object getSystemService(String name) {
665                 return mOriginalContext.getSystemService(name);
666             }
667         }
668 
669         @Override
onCreate(Bundle icicle)670         protected void onCreate(Bundle icicle) {
671             super.onCreate(icicle);
672             mEditText = new EditText(new Bug118341760ContextWrapper(this));
673             // Use SystemClock.elapsedRealtimeNanos()) as a unique ID of this edit text.
674             mEditText.setPrivateImeOptions(Long.toString(SystemClock.elapsedRealtimeNanos()));
675             final LinearLayout layout = new LinearLayout(this);
676             layout.setOrientation(LinearLayout.VERTICAL);
677             layout.addView(mEditText);
678             mEditText.requestFocus();
679             setContentView(layout);
680         }
681 
getEditText()682         EditText getEditText() {
683             return mEditText;
684         }
685     }
686 
assertImeWindowAndDisplayConfiguration( WindowState imeWinState, DisplayContent display)687     private void assertImeWindowAndDisplayConfiguration(
688             WindowState imeWinState, DisplayContent display) {
689         // The IME window should inherit the configuration from the IME DisplayArea.
690         final WindowManagerState.DisplayArea imeContainerDisplayArea = display.getImeContainer();
691         final Configuration configurationForIme = imeWinState.getMergedOverrideConfiguration();
692         final Configuration configurationForImeContainer =
693                 imeContainerDisplayArea.getMergedOverrideConfiguration();
694         final int displayDensityDpiForIme = configurationForIme.densityDpi;
695         final int displayDensityDpiForImeContainer = configurationForImeContainer.densityDpi;
696         final Rect displayBoundsForIme = configurationForIme.windowConfiguration.getBounds();
697         final Rect displayBoundsForImeContainer =
698                 configurationForImeContainer.windowConfiguration.getBounds();
699 
700         assertEquals("Display density not the same",
701                 displayDensityDpiForImeContainer, displayDensityDpiForIme);
702         assertEquals("Display bounds not the same",
703                 displayBoundsForImeContainer, displayBoundsForIme);
704     }
705 
tapAndAssertEditorFocusedOnImeActivity( TestActivitySession<? extends ImeTestActivity> activitySession, int expectDisplayId)706     private void tapAndAssertEditorFocusedOnImeActivity(
707             TestActivitySession<? extends ImeTestActivity> activitySession, int expectDisplayId) {
708         final int[] location = new int[2];
709         waitAndAssertActivityStateOnDisplay(activitySession.getActivity().getComponentName(),
710                 STATE_RESUMED, expectDisplayId,
711                 "ImeActivity failed to appear on display#" + expectDisplayId);
712         activitySession.runOnMainSyncAndWait(() -> {
713             final EditText editText = activitySession.getActivity().mEditText;
714             editText.getLocationOnScreen(location);
715         });
716         final ComponentName expectComponent = activitySession.getActivity().getComponentName();
717         tapOnDisplaySync(location[0], location[1], expectDisplayId);
718         mWmState.computeState(activitySession.getActivity().getComponentName());
719         mWmState.assertFocusedAppOnDisplay("Activity not focus on the display", expectComponent,
720                 expectDisplayId);
721     }
722 
showSoftInputAndAssertImeShownOnDisplay(int displayId, TestActivitySession<? extends ImeTestActivity> activitySession, ImeEventStream stream)723     private void showSoftInputAndAssertImeShownOnDisplay(int displayId,
724             TestActivitySession<? extends ImeTestActivity> activitySession, ImeEventStream stream)
725             throws Exception {
726         activitySession.runOnMainSyncAndWait(
727                 activitySession.getActivity()::showSoftInput);
728         expectEvent(stream, editorMatcher("onStartInputView",
729                 activitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
730         // Assert the IME is shown on the expected display.
731         mWmState.waitAndAssertImeWindowShownOnDisplay(displayId);
732     }
733 }
734