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.content.Intent.FLAG_ACTIVITY_NEW_TASK;
20 import static android.server.wm.CommandSession.ActivityCallback.ON_CONFIGURATION_CHANGED;
21 import static android.server.wm.CommandSession.ActivityCallback.ON_RESUME;
22 import static android.view.Display.DEFAULT_DISPLAY;
23 import static android.view.Display.INVALID_DISPLAY;
24 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
25 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
26 
27 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
28 
29 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
30 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
31 
32 import static org.junit.Assert.assertEquals;
33 import static org.junit.Assert.assertTrue;
34 import static org.junit.Assume.assumeTrue;
35 
36 import android.app.Activity;
37 import android.app.ActivityOptions;
38 import android.content.ComponentName;
39 import android.content.Context;
40 import android.content.Intent;
41 import android.os.Bundle;
42 import android.platform.test.annotations.Presubmit;
43 import android.server.wm.WindowManagerState.DisplayContent;
44 import android.view.Display;
45 import android.view.View;
46 import android.view.WindowManager;
47 import android.view.inputmethod.InputMethodManager;
48 import android.widget.EditText;
49 import android.widget.LinearLayout;
50 
51 import androidx.test.filters.MediumTest;
52 import androidx.test.rule.ActivityTestRule;
53 
54 import com.android.cts.mockime.ImeEventStream;
55 import com.android.cts.mockime.MockImeSession;
56 
57 import org.junit.Before;
58 import org.junit.Test;
59 
60 import java.util.concurrent.TimeUnit;
61 
62 /**
63  * Build/Install/Run:
64  *     atest CtsWindowManagerDeviceTestCases:MultiDisplayClientTests
65  */
66 @Presubmit
67 @MediumTest
68 @android.server.wm.annotation.Group3
69 public class MultiDisplayClientTests extends MultiDisplayTestBase {
70 
71     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(10); // 10 seconds
72     private static final String EXTRA_SHOW_IME = "show_ime";
73 
74     @Before
75     @Override
setUp()76     public void setUp() throws Exception {
77         super.setUp();
78         assumeTrue(supportsMultiDisplay());
79     }
80 
81     @Test
testDisplayIdUpdateOnMove_RelaunchActivity()82     public void testDisplayIdUpdateOnMove_RelaunchActivity() throws Exception {
83         testDisplayIdUpdateOnMove(ClientTestActivity.class, false /* handlesConfigChange */);
84     }
85 
86     @Test
testDisplayIdUpdateOnMove_NoRelaunchActivity()87     public void testDisplayIdUpdateOnMove_NoRelaunchActivity() throws Exception {
88         testDisplayIdUpdateOnMove(NoRelaunchActivity.class, true /* handlesConfigChange */);
89     }
90 
testDisplayIdUpdateOnMove(Class<T> activityClass, boolean handlesConfigChange)91     private <T extends Activity> void testDisplayIdUpdateOnMove(Class<T> activityClass,
92             boolean handlesConfigChange) throws Exception {
93         final ActivityTestRule<T> activityTestRule = new ActivityTestRule<>(
94                 activityClass, true /* initialTouchMode */, false /* launchActivity */);
95 
96         // Launch activity display.
97         separateTestJournal();
98         Activity activity = activityTestRule.launchActivity(new Intent());
99         final ComponentName activityName = activity.getComponentName();
100         waitAndAssertResume(activityName);
101 
102         // Create new simulated display
103         final DisplayContent newDisplay = createManagedVirtualDisplaySession()
104                 .setSimulateDisplay(true)
105                 .createDisplay();
106 
107         // Move the activity to the new secondary display.
108         separateTestJournal();
109         final ActivityOptions launchOptions = ActivityOptions.makeBasic();
110         final int displayId = newDisplay.mId;
111         launchOptions.setLaunchDisplayId(displayId);
112         final Intent newDisplayIntent = new Intent(mContext, activityClass);
113         newDisplayIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
114         getInstrumentation().getTargetContext().startActivity(newDisplayIntent,
115                 launchOptions.toBundle());
116         waitAndAssertTopResumedActivity(activityName, displayId,
117                 "Activity moved to secondary display must be focused");
118 
119         if (handlesConfigChange) {
120             // Wait for activity to receive the configuration change after move
121             waitAndAssertConfigurationChange(activityName);
122         } else {
123             // Activity will be re-created, wait for resumed state
124             waitAndAssertResume(activityName);
125             activity = activityTestRule.getActivity();
126         }
127 
128         final String suffix = " must be updated.";
129         assertEquals("Activity#getDisplayId()" + suffix, displayId, activity.getDisplayId());
130         assertEquals("Activity#getDisplay" + suffix,
131                 displayId, activity.getDisplay().getDisplayId());
132 
133         final WindowManager wm = activity.getWindowManager();
134         assertEquals("WM#getDefaultDisplay()" + suffix,
135                 displayId, wm.getDefaultDisplay().getDisplayId());
136 
137         final View view = activity.getWindow().getDecorView();
138         assertEquals("View#getDisplay()" + suffix,
139                 displayId, view.getDisplay().getDisplayId());
140     }
141 
142     @Test
testDisplayIdUpdateWhenImeMove_RelaunchActivity()143     public void testDisplayIdUpdateWhenImeMove_RelaunchActivity() throws Exception {
144         testDisplayIdUpdateWhenImeMove(ClientTestActivity.class);
145     }
146 
147     @Test
testDisplayIdUpdateWhenImeMove_NoRelaunchActivity()148     public void testDisplayIdUpdateWhenImeMove_NoRelaunchActivity() throws Exception {
149         testDisplayIdUpdateWhenImeMove(NoRelaunchActivity.class);
150     }
151 
testDisplayIdUpdateWhenImeMove(Class<? extends ImeTestActivity> activityClass)152     private void testDisplayIdUpdateWhenImeMove(Class<? extends ImeTestActivity> activityClass)
153             throws Exception {
154         assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());
155 
156         final VirtualDisplaySession virtualDisplaySession = createManagedVirtualDisplaySession();
157         final MockImeSession mockImeSession = MockImeHelper.createManagedMockImeSession(this);
158 
159         assertImeShownAndMatchesDisplayId(
160                 activityClass, mockImeSession, DEFAULT_DISPLAY);
161 
162         final DisplayContent newDisplay = virtualDisplaySession
163                 .setSimulateDisplay(true)
164                 .setShowSystemDecorations(true)
165                 .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL)
166                 .createDisplay();
167 
168         // Launch activity on the secondary display and make IME show.
169         assertImeShownAndMatchesDisplayId(
170                 activityClass, mockImeSession, newDisplay.mId);
171     }
172 
assertImeShownAndMatchesDisplayId(Class<? extends ImeTestActivity> activityClass, MockImeSession imeSession, int targetDisplayId)173     private  void assertImeShownAndMatchesDisplayId(Class<? extends ImeTestActivity> activityClass,
174             MockImeSession imeSession, int targetDisplayId) throws Exception {
175         final ImeEventStream stream = imeSession.openEventStream();
176 
177         final Intent intent = new Intent(mContext, activityClass)
178                 .putExtra(EXTRA_SHOW_IME, true).setFlags(FLAG_ACTIVITY_NEW_TASK);
179         separateTestJournal();
180         final ActivityOptions launchOptions = ActivityOptions.makeBasic();
181         launchOptions.setLaunchDisplayId(targetDisplayId);
182         getInstrumentation().getTargetContext().startActivity(intent, launchOptions.toBundle());
183 
184 
185         // Verify if IME is showed on the target display.
186         expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
187         mWmState.waitAndAssertImeWindowShownOnDisplay(targetDisplayId);
188 
189         final int imeDisplayId = expectCommand(stream, imeSession.callGetDisplayId(), TIMEOUT)
190                 .getReturnIntegerValue();
191         assertEquals("IME#getDisplayId() must match when IME move.",
192                 targetDisplayId, imeDisplayId);
193     }
194 
195     @Test
testInputMethodManagerDisplayId()196     public void testInputMethodManagerDisplayId() {
197         // Create a simulated display.
198         final DisplayContent newDisplay = createManagedVirtualDisplaySession()
199                 .setSimulateDisplay(true)
200                 .createDisplay();
201 
202         final Display display = mDm.getDisplay(newDisplay.mId);
203         final Context newDisplayContext = mContext.createDisplayContext(display);
204         final InputMethodManager imm = newDisplayContext.getSystemService(InputMethodManager.class);
205 
206         assertEquals("IMM#getDisplayId() must match.", newDisplay.mId, imm.getDisplayId());
207     }
208 
209     @Test
testViewGetDisplayOnPrimaryDisplay()210     public void testViewGetDisplayOnPrimaryDisplay() {
211         testViewGetDisplay(true /* isPrimary */);
212     }
213 
214     @Test
testViewGetDisplayOnSecondaryDisplay()215     public void testViewGetDisplayOnSecondaryDisplay() {
216         testViewGetDisplay(false /* isPrimary */);
217     }
218 
testViewGetDisplay(boolean isPrimary)219     private void testViewGetDisplay(boolean isPrimary) {
220         final TestActivitySession<ClientTestActivity> activitySession =
221                 createManagedTestActivitySession();
222         final DisplayContent newDisplay = createManagedVirtualDisplaySession()
223                 .setSimulateDisplay(true)
224                 .createDisplay();
225         final int displayId = isPrimary ? DEFAULT_DISPLAY : newDisplay.mId;
226 
227         separateTestJournal();
228         activitySession.launchTestActivityOnDisplaySync(ClientTestActivity.class, displayId);
229 
230         final Activity activity = activitySession.getActivity();
231         final ComponentName activityName = activity.getComponentName();
232 
233         waitAndAssertTopResumedActivity(activityName, displayId,
234                 "Activity launched on display:" + displayId + " must be focused");
235 
236         // Test View#getdisplay() from activity
237         final View view = activity.getWindow().getDecorView();
238         assertEquals("View#getDisplay() must match.", displayId, view.getDisplay().getDisplayId());
239 
240         final int[] resultDisplayId = { INVALID_DISPLAY };
241         activitySession.runOnMainAndAssertWithTimeout(
242                 () -> {
243                     // Test View#getdisplay() from WM#addView()
244                     final WindowManager wm = activity.getWindowManager();
245                     final View addedView = new View(activity);
246                     wm.addView(addedView, new WindowManager.LayoutParams());
247 
248                     // Get display ID from callback in case the added view has not be attached.
249                     addedView.addOnAttachStateChangeListener(
250                             new View.OnAttachStateChangeListener() {
251                                 @Override
252                                 public void onViewAttachedToWindow(View view) {
253                                     resultDisplayId[0] = view.getDisplay().getDisplayId();
254                                 }
255 
256                                 @Override
257                                 public void onViewDetachedFromWindow(View view) {}
258                             });
259 
260                     return displayId == resultDisplayId[0];
261                 }, TIMEOUT, "Display from added view must match. "
262                         + "Should be display:" + displayId
263                         + ", but was display:" + resultDisplayId[0]
264         );
265     }
266 
waitAndAssertConfigurationChange(ComponentName activityName)267     private void waitAndAssertConfigurationChange(ComponentName activityName) {
268         assertTrue("Must receive a single configuration change",
269                 mWmState.waitForWithAmState(
270                         state -> getCallbackCount(activityName, ON_CONFIGURATION_CHANGED) == 1,
271                         activityName + " receives configuration change"));
272     }
273 
waitAndAssertResume(ComponentName activityName)274     private void waitAndAssertResume(ComponentName activityName) {
275         assertTrue("Must be resumed once",
276                 mWmState.waitForWithAmState(
277                         state -> getCallbackCount(activityName, ON_RESUME) == 1,
278                         activityName + " performs resume"));
279     }
280 
getCallbackCount(ComponentName activityName, CommandSession.ActivityCallback callback)281     private static int getCallbackCount(ComponentName activityName,
282             CommandSession.ActivityCallback callback) {
283         final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(activityName);
284         return lifecycles.getCount(callback);
285     }
286 
287     public static class ClientTestActivity extends ImeTestActivity { }
288 
289     public static class NoRelaunchActivity extends ImeTestActivity { }
290 
291     public static class ImeTestActivity extends CommandSession.BasicTestActivity {
292         private EditText mEditText;
293         private boolean mShouldShowIme;
294 
295         @Override
onCreate(Bundle icicle)296         protected void onCreate(Bundle icicle) {
297             super.onCreate(icicle);
298             mShouldShowIme = getIntent().hasExtra(EXTRA_SHOW_IME);
299             if (mShouldShowIme) {
300                 mEditText = new EditText(this);
301                 final LinearLayout layout = new LinearLayout(this);
302                 layout.setOrientation(LinearLayout.VERTICAL);
303                 layout.addView(mEditText);
304                 setContentView(layout);
305             }
306         }
307 
308         @Override
onResume()309         protected void onResume() {
310             super.onResume();
311             if (mShouldShowIme) {
312                 getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
313                 mEditText.requestFocus();
314             }
315         }
316     }
317 }
318