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