1 /*
2  * Copyright (C) 2020 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 com.android.server.wm;
18 
19 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
20 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
21 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
22 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
23 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
24 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
25 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
26 import static android.view.Surface.ROTATION_90;
27 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
28 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
29 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
30 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
31 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
32 import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
33 import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER;
34 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
35 import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
36 
37 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
38 import static com.android.server.wm.SizeCompatTests.prepareLimitedBounds;
39 import static com.android.server.wm.SizeCompatTests.prepareUnresizable;
40 import static com.android.server.wm.SizeCompatTests.rotateDisplay;
41 
42 import static com.google.common.truth.Truth.assertThat;
43 
44 import static org.mockito.ArgumentMatchers.any;
45 import static org.mockito.Mockito.doReturn;
46 import static org.mockito.Mockito.mock;
47 import static org.mockito.Mockito.never;
48 import static org.mockito.Mockito.verify;
49 import static org.mockito.Mockito.when;
50 
51 import android.annotation.NonNull;
52 import android.content.res.Configuration;
53 import android.graphics.Rect;
54 import android.os.Binder;
55 import android.platform.test.annotations.Presubmit;
56 import android.view.Display;
57 import android.window.IDisplayAreaOrganizer;
58 
59 import androidx.test.filters.SmallTest;
60 
61 import com.google.android.collect.Lists;
62 
63 import org.junit.Before;
64 import org.junit.Test;
65 import org.junit.runner.RunWith;
66 
67 import java.util.ArrayList;
68 import java.util.List;
69 
70 /**
71  * Tests for the Dual DisplayAreaGroup device behavior.
72  *
73  * Build/Install/Run:
74  *  atest WmTests:DualDisplayAreaGroupPolicyTest
75  */
76 @SmallTest
77 @Presubmit
78 @RunWith(WindowTestRunner.class)
79 public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase {
80     private static final int FEATURE_FIRST_ROOT = FEATURE_VENDOR_FIRST;
81     private static final int FEATURE_FIRST_TASK_CONTAINER = FEATURE_DEFAULT_TASK_CONTAINER;
82     private static final int FEATURE_SECOND_ROOT = FEATURE_VENDOR_FIRST + 1;
83     private static final int FEATURE_SECOND_TASK_CONTAINER = FEATURE_VENDOR_FIRST + 2;
84 
85     private DualDisplayContent mDisplay;
86     private DisplayAreaGroup mFirstRoot;
87     private DisplayAreaGroup mSecondRoot;
88     private TaskDisplayArea mFirstTda;
89     private TaskDisplayArea mSecondTda;
90     private Task mFirstTask;
91     private Task mSecondTask;
92     private ActivityRecord mFirstActivity;
93     private ActivityRecord mSecondActivity;
94 
95     @Before
setUp()96     public void setUp() {
97         // Let the Display to be created with the DualDisplay policy.
98         setupDisplay(new DualDisplayTestPolicyProvider(mWm));
99     }
100 
101     /** Populates fields for the test display. */
setupDisplay(@onNull DisplayAreaPolicy.Provider policyProvider)102     private void setupDisplay(@NonNull DisplayAreaPolicy.Provider policyProvider) {
103         doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider();
104 
105         // Display: 1920x1200 (landscape). First and second display are both 860x1200 (portrait).
106         mDisplay = new DualDisplayContent.Builder(mAtm, 1920, 1200).build();
107         mFirstRoot = mDisplay.mFirstRoot;
108         mSecondRoot = mDisplay.mSecondRoot;
109         mFirstTda = mDisplay.getTaskDisplayArea(FEATURE_FIRST_TASK_CONTAINER);
110         mSecondTda = mDisplay.getTaskDisplayArea(FEATURE_SECOND_TASK_CONTAINER);
111         mFirstTask = new TaskBuilder(mSupervisor)
112                 .setTaskDisplayArea(mFirstTda)
113                 .setCreateActivity(true)
114                 .build()
115                 .getBottomMostTask();
116         mSecondTask = new TaskBuilder(mSupervisor)
117                 .setTaskDisplayArea(mSecondTda)
118                 .setCreateActivity(true)
119                 .build()
120                 .getBottomMostTask();
121         mFirstActivity = mFirstTask.getTopNonFinishingActivity();
122         mSecondActivity = mSecondTask.getTopNonFinishingActivity();
123 
124         spyOn(mDisplay);
125         spyOn(mFirstRoot);
126         spyOn(mSecondRoot);
127     }
128 
129     @Test
testNotIgnoreOrientationRequest_differentOrientationFromDisplay_reversesRequest()130     public void testNotIgnoreOrientationRequest_differentOrientationFromDisplay_reversesRequest() {
131         mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
132         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
133 
134         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LANDSCAPE);
135 
136         assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT);
137         assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
138 
139         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_PORTRAIT);
140 
141         assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
142         assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
143     }
144 
145     @Test
testNotIgnoreOrientationRequest_onlyRespectsFocusedTaskDisplayArea()146     public void testNotIgnoreOrientationRequest_onlyRespectsFocusedTaskDisplayArea() {
147         mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
148         mSecondRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
149         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
150 
151         // Second TDA is not focused, so Display won't get the request
152         prepareUnresizable(mSecondActivity, SCREEN_ORIENTATION_LANDSCAPE);
153 
154         assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_UNSPECIFIED);
155 
156         // First TDA is focused, so Display gets the request
157         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LANDSCAPE);
158 
159         assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT);
160     }
161 
162     @Test
testIgnoreOrientationRequest_displayDoesNotReceiveOrientationChange()163     public void testIgnoreOrientationRequest_displayDoesNotReceiveOrientationChange() {
164         mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
165         mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
166         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
167 
168         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LANDSCAPE);
169 
170         verify(mFirstRoot).onDescendantOrientationChanged(any());
171         verify(mDisplay, never()).onDescendantOrientationChanged(any());
172     }
173 
174     @Test
testIgnoreOrientationRequest_displayReceiveOrientationChangeForNoSensor()175     public void testIgnoreOrientationRequest_displayReceiveOrientationChangeForNoSensor() {
176         mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
177         mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
178         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
179 
180         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_NOSENSOR);
181 
182         verify(mFirstRoot).onDescendantOrientationChanged(any());
183         verify(mDisplay).onDescendantOrientationChanged(any());
184     }
185 
186     @Test
testIgnoreOrientationRequest_displayReceiveOrientationChangeForLocked()187     public void testIgnoreOrientationRequest_displayReceiveOrientationChangeForLocked() {
188         mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
189         mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
190         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
191 
192         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LOCKED);
193 
194         verify(mFirstRoot).onDescendantOrientationChanged(any());
195         verify(mDisplay).onDescendantOrientationChanged(any());
196     }
197 
198     @Test
testLaunchPortraitApp_fillsDisplayAreaGroup()199     public void testLaunchPortraitApp_fillsDisplayAreaGroup() {
200         mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
201         mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
202         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
203 
204         prepareLimitedBounds(mFirstActivity, SCREEN_ORIENTATION_PORTRAIT,
205                 false /* isUnresizable */);
206         final Rect dagBounds = new Rect(mFirstRoot.getBounds());
207         final Rect taskBounds = new Rect(mFirstTask.getBounds());
208         final Rect activityBounds = new Rect(mFirstActivity.getBounds());
209 
210         // DAG is portrait (860x1200), so Task and Activity fill DAG.
211         assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
212         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
213         assertThat(taskBounds).isEqualTo(dagBounds);
214         assertThat(activityBounds).isEqualTo(taskBounds);
215     }
216 
217     @Test
testLaunchPortraitApp_sizeCompatAfterRotation()218     public void testLaunchPortraitApp_sizeCompatAfterRotation() {
219         mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
220         mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
221         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
222 
223         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_PORTRAIT);
224         final Rect dagBounds = new Rect(mFirstRoot.getBounds());
225         final Rect activityBounds = new Rect(mFirstActivity.getBounds());
226 
227         rotateDisplay(mDisplay, ROTATION_90);
228         final Rect newDagBounds = new Rect(mFirstRoot.getBounds());
229         final Rect newTaskBounds = new Rect(mFirstTask.getBounds());
230         final Rect activitySizeCompatBounds = new Rect(mFirstActivity.getBounds());
231         final Rect activityConfigBounds =
232                 new Rect(mFirstActivity.getConfiguration().windowConfiguration.getBounds());
233 
234         // DAG is landscape (1200x860), no fixed orientation letterbox
235         assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
236         assertThat(mFirstActivity.inSizeCompatMode()).isTrue();
237         assertThat(newDagBounds.width()).isEqualTo(dagBounds.height());
238         assertThat(newDagBounds.height()).isEqualTo(dagBounds.width());
239         assertThat(newTaskBounds).isEqualTo(newDagBounds);
240 
241         // Activity config bounds is unchanged, size compat bounds is (860x[860x860/1200=616])
242         assertThat(mFirstActivity.getCompatScale()).isLessThan(1f);
243         assertThat(activityConfigBounds.width()).isEqualTo(activityBounds.width());
244         assertThat(activityConfigBounds.height()).isEqualTo(activityBounds.height());
245         assertThat(activitySizeCompatBounds.height()).isEqualTo(newTaskBounds.height());
246         assertThat(activitySizeCompatBounds.width()).isEqualTo(
247                 newTaskBounds.height() * newTaskBounds.height() / newTaskBounds.width());
248     }
249 
250     @Test
testLaunchNoSensorApp_noSizeCompatAfterRotation()251     public void testLaunchNoSensorApp_noSizeCompatAfterRotation() {
252         mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
253         mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
254         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
255 
256         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_NOSENSOR);
257         assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
258         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
259 
260         rotateDisplay(mDisplay, ROTATION_90);
261         assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
262         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
263     }
264 
265     @Test
testLaunchLandscapeApp_activityIsLetterboxForFixedOrientationInDisplayAreaGroup()266     public void testLaunchLandscapeApp_activityIsLetterboxForFixedOrientationInDisplayAreaGroup() {
267         mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
268         mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
269         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
270 
271         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LANDSCAPE);
272         final Rect dagBounds = new Rect(mFirstRoot.getBounds());
273         final Rect taskBounds = new Rect(mFirstTask.getBounds());
274         final Rect activityBounds = new Rect(mFirstActivity.getBounds());
275 
276         // DAG is portrait (860x1200), and activity is letterboxed for fixed orientation
277         // (860x[860x860/1200=616]). Task fills DAG.
278         assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isTrue();
279         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
280         assertThat(taskBounds).isEqualTo(dagBounds);
281         assertThat(activityBounds.width()).isEqualTo(dagBounds.width());
282         assertThat(activityBounds.height())
283                 .isEqualTo(dagBounds.width() * dagBounds.width() / dagBounds.height());
284     }
285 
286     @Test
testLaunchNoSensorApp_activityIsNotLetterboxForFixedOrientationDisplayAreaGroup()287     public void testLaunchNoSensorApp_activityIsNotLetterboxForFixedOrientationDisplayAreaGroup() {
288         mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
289         mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
290         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
291 
292         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_NOSENSOR);
293         assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
294     }
295 
296     @Test
testLaunchLockedApp_activityIsNotLetterboxForFixedOrientationInDisplayAreaGroup()297     public void testLaunchLockedApp_activityIsNotLetterboxForFixedOrientationInDisplayAreaGroup() {
298         mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
299         mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
300         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
301 
302         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LOCKED);
303         assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
304     }
305 
306     @Test
testLaunchLandscapeApp_fixedOrientationLetterboxBecomesSizeCompatAfterRotation()307     public void testLaunchLandscapeApp_fixedOrientationLetterboxBecomesSizeCompatAfterRotation() {
308         mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
309         mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
310         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
311 
312         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LANDSCAPE);
313         final Rect dagBounds = new Rect(mFirstRoot.getBounds());
314         final Rect activityBounds = new Rect(mFirstActivity.getBounds());
315 
316         rotateDisplay(mDisplay, ROTATION_90);
317         final Rect newDagBounds = new Rect(mFirstRoot.getBounds());
318         final Rect newTaskBounds = new Rect(mFirstTask.getBounds());
319         final Rect newActivityBounds = new Rect(mFirstActivity.getBounds());
320 
321         // DAG is landscape (1200x860), no fixed orientation letterbox
322         assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
323         assertThat(mFirstActivity.inSizeCompatMode()).isTrue();
324         assertThat(newDagBounds.width()).isEqualTo(dagBounds.height());
325         assertThat(newDagBounds.height()).isEqualTo(dagBounds.width());
326         assertThat(newTaskBounds).isEqualTo(newDagBounds);
327 
328         // Because we don't scale up, there is no size compat bounds and app bounds is the same as
329         // the previous bounds.
330         assertThat(mFirstActivity.hasSizeCompatBounds()).isFalse();
331         assertThat(newActivityBounds.width()).isEqualTo(activityBounds.width());
332         assertThat(newActivityBounds.height()).isEqualTo(activityBounds.height());
333     }
334 
335     @Test
testLaunchNoSensorApp_fixedOrientationLetterboxBecomesSizeCompatAfterRotation()336     public void testLaunchNoSensorApp_fixedOrientationLetterboxBecomesSizeCompatAfterRotation() {
337         mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
338         mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
339         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
340 
341         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_NOSENSOR);
342 
343         rotateDisplay(mDisplay, ROTATION_90);
344 
345         assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
346         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
347     }
348 
349     @Test
testPlaceImeContainer_reparentToTargetDisplayAreaGroup()350     public void testPlaceImeContainer_reparentToTargetDisplayAreaGroup() {
351         setupImeWindow();
352         final DisplayArea.Tokens imeContainer = mDisplay.getImeContainer();
353         final WindowToken imeToken = tokenOfType(TYPE_INPUT_METHOD);
354 
355         // By default, the ime container is attached to DC as defined in DAPolicy.
356         assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mDisplay);
357         assertThat(mDisplay.findAreaForTokenInLayer(imeToken)).isEqualTo(imeContainer);
358 
359         final WindowState firstActivityWin =
360                 createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity,
361                         "firstActivityWin");
362         spyOn(firstActivityWin);
363         final WindowState secondActivityWin =
364                 createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mSecondActivity,
365                         "firstActivityWin");
366         spyOn(secondActivityWin);
367 
368         // firstActivityWin should be the target
369         doReturn(true).when(firstActivityWin).canBeImeTarget();
370         doReturn(false).when(secondActivityWin).canBeImeTarget();
371 
372         WindowState imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */);
373 
374         assertThat(imeTarget).isEqualTo(firstActivityWin);
375         verify(mFirstRoot).placeImeContainer(imeContainer);
376         assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mFirstRoot);
377         assertThat(imeContainer.getParent().asDisplayArea().mFeatureId)
378                 .isEqualTo(FEATURE_IME_PLACEHOLDER);
379         assertThat(mDisplay.findAreaForTokenInLayer(imeToken)).isNull();
380         assertThat(mFirstRoot.findAreaForTokenInLayer(imeToken)).isEqualTo(imeContainer);
381         assertThat(mSecondRoot.findAreaForTokenInLayer(imeToken)).isNull();
382 
383         // secondActivityWin should be the target
384         doReturn(false).when(firstActivityWin).canBeImeTarget();
385         doReturn(true).when(secondActivityWin).canBeImeTarget();
386 
387         imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */);
388 
389         assertThat(imeTarget).isEqualTo(secondActivityWin);
390         verify(mSecondRoot).placeImeContainer(imeContainer);
391         assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondRoot);
392         assertThat(imeContainer.getParent().asDisplayArea().mFeatureId)
393                 .isEqualTo(FEATURE_IME_PLACEHOLDER);
394         assertThat(mDisplay.findAreaForTokenInLayer(imeToken)).isNull();
395         assertThat(mFirstRoot.findAreaForTokenInLayer(imeToken)).isNull();
396         assertThat(mSecondRoot.findAreaForTokenInLayer(imeToken)).isEqualTo(imeContainer);
397     }
398 
399     @Test
testPlaceImeContainer_hidesImeWhenParentChanges()400     public void testPlaceImeContainer_hidesImeWhenParentChanges() {
401         setupImeWindow();
402         final DisplayArea.Tokens imeContainer = mDisplay.getImeContainer();
403         final WindowToken imeToken = tokenOfType(TYPE_INPUT_METHOD);
404         final WindowState firstActivityWin =
405                 createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity,
406                         "firstActivityWin");
407         spyOn(firstActivityWin);
408         final WindowState secondActivityWin =
409                 createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mSecondActivity,
410                         "secondActivityWin");
411         spyOn(secondActivityWin);
412 
413         // firstActivityWin should be the target
414         doReturn(true).when(firstActivityWin).canBeImeTarget();
415         doReturn(false).when(secondActivityWin).canBeImeTarget();
416 
417         WindowState imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */);
418         assertThat(imeTarget).isEqualTo(firstActivityWin);
419         verify(mFirstRoot).placeImeContainer(imeContainer);
420 
421         // secondActivityWin should be the target
422         doReturn(false).when(firstActivityWin).canBeImeTarget();
423         doReturn(true).when(secondActivityWin).canBeImeTarget();
424 
425         spyOn(mDisplay.mInputMethodWindow);
426         imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */);
427 
428         assertThat(imeTarget).isEqualTo(secondActivityWin);
429         verify(mSecondRoot).placeImeContainer(imeContainer);
430         // verify hide() was called on InputMethodWindow.
431         verify(mDisplay.mInputMethodWindow).hide(false /* doAnimation */, false /* requestAnim */);
432     }
433 
434     @Test
testPlaceImeContainer_skipReparentForOrganizedImeContainer()435     public void testPlaceImeContainer_skipReparentForOrganizedImeContainer() {
436         setupImeWindow();
437         final DisplayArea.Tokens imeContainer = mDisplay.getImeContainer();
438         final WindowToken imeToken = tokenOfType(TYPE_INPUT_METHOD);
439 
440         // By default, the ime container is attached to DC as defined in DAPolicy.
441         assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mDisplay);
442         assertThat(mDisplay.findAreaForTokenInLayer(imeToken)).isEqualTo(imeContainer);
443 
444         final WindowState firstActivityWin =
445                 createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity,
446                         "firstActivityWin");
447         spyOn(firstActivityWin);
448         // firstActivityWin should be the target
449         doReturn(true).when(firstActivityWin).canBeImeTarget();
450 
451         // Main precondition for this test: organize the ImeContainer.
452         final IDisplayAreaOrganizer mockImeOrganizer = mock(IDisplayAreaOrganizer.class);
453         when(mockImeOrganizer.asBinder()).thenReturn(new Binder());
454         imeContainer.setOrganizer(mockImeOrganizer);
455 
456         WindowState imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */);
457 
458         // The IME target must be updated but the don't reparent organized ImeContainers.
459         // See DisplayAreaOrganizer#FEATURE_IME.
460         assertThat(imeTarget).isEqualTo(firstActivityWin);
461         verify(mFirstRoot, never()).placeImeContainer(imeContainer);
462 
463         // Clean up organizer.
464         imeContainer.setOrganizer(null);
465     }
466 
467     @Test
testPlaceImeContainer_noReparentIfRootDoesNotHaveImePlaceholder()468     public void testPlaceImeContainer_noReparentIfRootDoesNotHaveImePlaceholder() {
469         // Define the DualDisplayArea hierarchy without IME_PLACEHOLDER in DAGs.
470         setupDisplay(new DualDisplayTestPolicyProvider(new ArrayList<>(), new ArrayList<>()));
471         setupImeWindow();
472         final DisplayArea.Tokens imeContainer = mDisplay.getImeContainer();
473         final WindowToken imeToken = tokenOfType(TYPE_INPUT_METHOD);
474 
475         // By default, the ime container is attached to DC as defined in DAPolicy.
476         assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mDisplay);
477 
478         // firstActivityWin should be the target
479         final WindowState firstActivityWin =
480                 createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity,
481                         "firstActivityWin");
482         spyOn(firstActivityWin);
483         doReturn(true).when(firstActivityWin).canBeImeTarget();
484         WindowState imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */);
485 
486         // There is no IME_PLACEHOLDER in the firstRoot, so the ImeContainer will not be reparented.
487         assertThat(imeTarget).isEqualTo(firstActivityWin);
488         verify(mFirstRoot).placeImeContainer(imeContainer);
489         assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mDisplay);
490         assertThat(imeContainer.getParent().asDisplayArea().mFeatureId)
491                 .isEqualTo(FEATURE_IME_PLACEHOLDER);
492         assertThat(mDisplay.findAreaForTokenInLayer(imeToken)).isEqualTo(imeContainer);
493         assertThat(mFirstRoot.findAreaForTokenInLayer(imeToken)).isNull();
494         assertThat(mSecondRoot.findAreaForTokenInLayer(imeToken)).isNull();
495     }
496 
497     @Test
testResizableFixedOrientationApp_fixedOrientationLetterboxing()498     public void testResizableFixedOrientationApp_fixedOrientationLetterboxing() {
499         mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
500         mSecondRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
501 
502         // Launch portrait on first DAG
503         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
504         prepareLimitedBounds(mFirstActivity, SCREEN_ORIENTATION_PORTRAIT,
505                 false /* isUnresizable */);
506 
507         // Display in landscape (as opposite to DAG), first DAG and activity in portrait
508         assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
509         assertThat(mFirstRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
510         assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
511         assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
512         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
513 
514         // Launch portrait on second DAG
515         mDisplay.onLastFocusedTaskDisplayAreaChanged(mSecondTda);
516         prepareLimitedBounds(mSecondActivity, SCREEN_ORIENTATION_LANDSCAPE,
517                 false /* isUnresizable */);
518 
519         // Display in portrait (as opposite to DAG), first DAG and activity in landscape
520         assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT);
521         assertThat(mSecondRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
522         assertThat(mSecondActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
523         assertThat(mSecondActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
524         assertThat(mSecondActivity.inSizeCompatMode()).isFalse();
525 
526         // First activity is letterboxed in portrait as requested.
527         assertThat(mFirstRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
528         assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
529         assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isTrue();
530         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
531 
532     }
533 
setupImeWindow()534     private void setupImeWindow() {
535         final WindowState imeWindow = createWindow(null /* parent */,
536                 TYPE_INPUT_METHOD, mDisplay, "mImeWindow");
537         imeWindow.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
538         mDisplay.mInputMethodWindow = imeWindow;
539     }
540 
tokenOfType(int type)541     private WindowToken tokenOfType(int type) {
542         return new WindowToken.Builder(mWm, new Binder(), type)
543                 .setDisplayContent(mDisplay).build();
544     }
545 
546     /** Display with two {@link DisplayAreaGroup}. Each of them take half of the screen. */
547     static class DualDisplayContent extends TestDisplayContent {
548         final DisplayAreaGroup mFirstRoot;
549         final DisplayAreaGroup mSecondRoot;
550         final Rect mLastDisplayBounds;
551 
552         /** Please use the {@link Builder} to create. */
DualDisplayContent(RootWindowContainer rootWindowContainer, Display display)553         DualDisplayContent(RootWindowContainer rootWindowContainer,
554                 Display display) {
555             super(rootWindowContainer, display, mock(DeviceStateController.class));
556 
557             mFirstRoot = getGroupRoot(FEATURE_FIRST_ROOT);
558             mSecondRoot = getGroupRoot(FEATURE_SECOND_ROOT);
559             mLastDisplayBounds = new Rect(getBounds());
560             updateDisplayAreaGroupBounds();
561         }
562 
getGroupRoot(int rootFeatureId)563         DisplayAreaGroup getGroupRoot(int rootFeatureId) {
564             DisplayArea da = getDisplayArea(rootFeatureId);
565             assertThat(da).isInstanceOf(DisplayAreaGroup.class);
566             return (DisplayAreaGroup) da;
567         }
568 
getTaskDisplayArea(int tdaFeatureId)569         TaskDisplayArea getTaskDisplayArea(int tdaFeatureId) {
570             DisplayArea da = getDisplayArea(tdaFeatureId);
571             assertThat(da).isInstanceOf(TaskDisplayArea.class);
572             return (TaskDisplayArea) da;
573         }
574 
getDisplayArea(int featureId)575         DisplayArea getDisplayArea(int featureId) {
576             final DisplayArea displayArea =
577                     getItemFromDisplayAreas(da -> da.mFeatureId == featureId ? da : null);
578             assertThat(displayArea).isNotNull();
579             return displayArea;
580         }
581 
582         @Override
onConfigurationChanged(Configuration newParentConfig)583         public void onConfigurationChanged(Configuration newParentConfig) {
584             super.onConfigurationChanged(newParentConfig);
585 
586             final Rect curBounds = getBounds();
587             if (mLastDisplayBounds != null && !mLastDisplayBounds.equals(curBounds)) {
588                 mLastDisplayBounds.set(curBounds);
589                 updateDisplayAreaGroupBounds();
590             }
591         }
592 
593         /** Updates first and second {@link DisplayAreaGroup} to take half of the screen. */
updateDisplayAreaGroupBounds()594         private void updateDisplayAreaGroupBounds() {
595             if (mFirstRoot == null || mSecondRoot == null) {
596                 return;
597             }
598 
599             final Rect bounds = mLastDisplayBounds;
600             Rect groupBounds1, groupBounds2;
601             if (bounds.width() >= bounds.height()) {
602                 groupBounds1 = new Rect(bounds.left, bounds.top,
603                         (bounds.right + bounds.left) / 2, bounds.bottom);
604 
605                 groupBounds2 = new Rect((bounds.right + bounds.left) / 2, bounds.top,
606                         bounds.right, bounds.bottom);
607             } else {
608                 groupBounds1 = new Rect(bounds.left, bounds.top,
609                         bounds.right, (bounds.top + bounds.bottom) / 2);
610 
611                 groupBounds2 = new Rect(bounds.left,
612                         (bounds.top + bounds.bottom) / 2, bounds.right, bounds.bottom);
613             }
614             mFirstRoot.setBounds(groupBounds1);
615             mSecondRoot.setBounds(groupBounds2);
616         }
617 
618         static class Builder extends TestDisplayContent.Builder {
619 
Builder(ActivityTaskManagerService service, int width, int height)620             Builder(ActivityTaskManagerService service, int width, int height) {
621                 super(service, width, height);
622             }
623 
624             @Override
createInternal(Display display)625             TestDisplayContent createInternal(Display display) {
626                 return new DualDisplayContent(mService.mRootWindowContainer, display);
627             }
628 
build()629             DualDisplayContent build() {
630                 return (DualDisplayContent) super.build();
631             }
632         }
633     }
634 
635     /** Policy to create a dual {@link DisplayAreaGroup} policy in test. */
636     static class DualDisplayTestPolicyProvider implements DisplayAreaPolicy.Provider {
637 
638         @NonNull
639         private final List<DisplayAreaPolicyBuilder.Feature> mFirstRootFeatures = new ArrayList<>();
640         @NonNull
641         private final List<DisplayAreaPolicyBuilder.Feature> mSecondRootFeatures =
642                 new ArrayList<>();
643 
DualDisplayTestPolicyProvider(@onNull WindowManagerService wmService)644         DualDisplayTestPolicyProvider(@NonNull WindowManagerService wmService) {
645             // Add IME_PLACEHOLDER by default.
646             this(Lists.newArrayList(new DisplayAreaPolicyBuilder.Feature.Builder(
647                             wmService.mPolicy,
648                             "ImePlaceholder", FEATURE_IME_PLACEHOLDER)
649                             .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG)
650                             .build()),
651                     Lists.newArrayList(new DisplayAreaPolicyBuilder.Feature.Builder(
652                             wmService.mPolicy,
653                             "ImePlaceholder", FEATURE_IME_PLACEHOLDER)
654                             .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG)
655                             .build()));
656         }
657 
DualDisplayTestPolicyProvider( @onNull List<DisplayAreaPolicyBuilder.Feature> firstRootFeatures, @NonNull List<DisplayAreaPolicyBuilder.Feature> secondRootFeatures)658         DualDisplayTestPolicyProvider(
659                 @NonNull List<DisplayAreaPolicyBuilder.Feature> firstRootFeatures,
660                 @NonNull List<DisplayAreaPolicyBuilder.Feature> secondRootFeatures) {
661             mFirstRootFeatures.addAll(firstRootFeatures);
662             mSecondRootFeatures.addAll(secondRootFeatures);
663         }
664 
665         @Override
instantiate(@onNull WindowManagerService wmService, @NonNull DisplayContent content, @NonNull RootDisplayArea root, @NonNull DisplayArea.Tokens imeContainer)666         public DisplayAreaPolicy instantiate(@NonNull WindowManagerService wmService,
667                 @NonNull DisplayContent content, @NonNull RootDisplayArea root,
668                 @NonNull DisplayArea.Tokens imeContainer) {
669             // Root
670             // Include FEATURE_WINDOWED_MAGNIFICATION because it will be used as the screen rotation
671             // layer
672             DisplayAreaPolicyBuilder.HierarchyBuilder rootHierarchy =
673                     new DisplayAreaPolicyBuilder.HierarchyBuilder(root)
674                             .setImeContainer(imeContainer)
675                             .addFeature(new DisplayAreaPolicyBuilder.Feature.Builder(
676                                     wmService.mPolicy,
677                                     "WindowedMagnification", FEATURE_WINDOWED_MAGNIFICATION)
678                                     .upTo(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY)
679                                     .except(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY)
680                                     .setNewDisplayAreaSupplier(DisplayArea.Dimmable::new)
681                                     .build())
682                             .addFeature(new DisplayAreaPolicyBuilder.Feature.Builder(
683                                     wmService.mPolicy,
684                                     "ImePlaceholder", FEATURE_IME_PLACEHOLDER)
685                                     .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG)
686                                     .build());
687 
688             // First
689             final RootDisplayArea firstRoot = new DisplayAreaGroup(wmService, "FirstRoot",
690                     FEATURE_FIRST_ROOT);
691             final TaskDisplayArea firstTaskDisplayArea = new TaskDisplayArea(content, wmService,
692                     "FirstTaskDisplayArea", FEATURE_FIRST_TASK_CONTAINER);
693             final List<TaskDisplayArea> firstTdaList = new ArrayList<>();
694             firstTdaList.add(firstTaskDisplayArea);
695             DisplayAreaPolicyBuilder.HierarchyBuilder firstHierarchy =
696                     new DisplayAreaPolicyBuilder.HierarchyBuilder(firstRoot)
697                             .setTaskDisplayAreas(firstTdaList);
698             for (DisplayAreaPolicyBuilder.Feature feature : mFirstRootFeatures) {
699                 firstHierarchy.addFeature(feature);
700             }
701 
702             // Second
703             final RootDisplayArea secondRoot = new DisplayAreaGroup(wmService, "SecondRoot",
704                     FEATURE_SECOND_ROOT);
705             final TaskDisplayArea secondTaskDisplayArea = new TaskDisplayArea(content, wmService,
706                     "SecondTaskDisplayArea", FEATURE_SECOND_TASK_CONTAINER);
707             final List<TaskDisplayArea> secondTdaList = new ArrayList<>();
708             secondTdaList.add(secondTaskDisplayArea);
709             DisplayAreaPolicyBuilder.HierarchyBuilder secondHierarchy =
710                     new DisplayAreaPolicyBuilder.HierarchyBuilder(secondRoot)
711                             .setTaskDisplayAreas(secondTdaList);
712             for (DisplayAreaPolicyBuilder.Feature feature : mSecondRootFeatures) {
713                 secondHierarchy.addFeature(feature);
714             }
715 
716             return new DisplayAreaPolicyBuilder()
717                     .setRootHierarchy(rootHierarchy)
718                     .addDisplayAreaGroupHierarchy(firstHierarchy)
719                     .addDisplayAreaGroupHierarchy(secondHierarchy)
720                     .build(wmService);
721         }
722     }
723 }
724