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_PORTRAIT;
21 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
22 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
23 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
24 import static android.view.Surface.ROTATION_90;
25 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
26 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
27 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
28 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
29 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
30 import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
31 import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER;
32 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
33 import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
34 
35 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
36 import static com.android.server.wm.SizeCompatTests.prepareLimitedBounds;
37 import static com.android.server.wm.SizeCompatTests.prepareUnresizable;
38 import static com.android.server.wm.SizeCompatTests.rotateDisplay;
39 
40 import static com.google.common.truth.Truth.assertThat;
41 
42 import static org.mockito.ArgumentMatchers.any;
43 import static org.mockito.Mockito.doReturn;
44 import static org.mockito.Mockito.never;
45 import static org.mockito.Mockito.verify;
46 
47 import android.content.res.Configuration;
48 import android.graphics.Rect;
49 import android.os.Binder;
50 import android.platform.test.annotations.Presubmit;
51 import android.view.Display;
52 
53 import androidx.test.filters.SmallTest;
54 
55 import org.junit.Before;
56 import org.junit.Test;
57 import org.junit.runner.RunWith;
58 
59 import java.util.ArrayList;
60 import java.util.List;
61 
62 /**
63  * Tests for the Dual DisplayAreaGroup device behavior.
64  *
65  * Build/Install/Run:
66  *  atest WmTests:DualDisplayAreaGroupPolicyTest
67  */
68 @SmallTest
69 @Presubmit
70 @RunWith(WindowTestRunner.class)
71 public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase {
72     private static final int FEATURE_FIRST_ROOT = FEATURE_VENDOR_FIRST;
73     private static final int FEATURE_FIRST_TASK_CONTAINER = FEATURE_DEFAULT_TASK_CONTAINER;
74     private static final int FEATURE_SECOND_ROOT = FEATURE_VENDOR_FIRST + 1;
75     private static final int FEATURE_SECOND_TASK_CONTAINER = FEATURE_VENDOR_FIRST + 2;
76 
77     private DualDisplayContent mDisplay;
78     private DisplayAreaGroup mFirstRoot;
79     private DisplayAreaGroup mSecondRoot;
80     private TaskDisplayArea mFirstTda;
81     private TaskDisplayArea mSecondTda;
82     private Task mFirstTask;
83     private Task mSecondTask;
84     private ActivityRecord mFirstActivity;
85     private ActivityRecord mSecondActivity;
86 
87     @Before
setUp()88     public void setUp() {
89         // Let the Display to be created with the DualDisplay policy.
90         final DisplayAreaPolicy.Provider policyProvider = new DualDisplayTestPolicyProvider();
91         doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider();
92 
93         // Display: 1920x1200 (landscape). First and second display are both 860x1200 (portrait).
94         mDisplay = new DualDisplayContent.Builder(mAtm, 1920, 1200).build();
95         mFirstRoot = mDisplay.mFirstRoot;
96         mSecondRoot = mDisplay.mSecondRoot;
97         mFirstTda = mDisplay.getTaskDisplayArea(FEATURE_FIRST_TASK_CONTAINER);
98         mSecondTda = mDisplay.getTaskDisplayArea(FEATURE_SECOND_TASK_CONTAINER);
99         mFirstTask = new TaskBuilder(mSupervisor)
100                 .setTaskDisplayArea(mFirstTda)
101                 .setCreateActivity(true)
102                 .build()
103                 .getBottomMostTask();
104         mSecondTask = new TaskBuilder(mSupervisor)
105                 .setTaskDisplayArea(mSecondTda)
106                 .setCreateActivity(true)
107                 .build()
108                 .getBottomMostTask();
109         mFirstActivity = mFirstTask.getTopNonFinishingActivity();
110         mSecondActivity = mSecondTask.getTopNonFinishingActivity();
111 
112         spyOn(mDisplay);
113         spyOn(mFirstRoot);
114         spyOn(mSecondRoot);
115     }
116 
117     @Test
testNotIgnoreOrientationRequest_differentOrientationFromDisplay_reversesRequest()118     public void testNotIgnoreOrientationRequest_differentOrientationFromDisplay_reversesRequest() {
119         mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
120         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
121 
122         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LANDSCAPE);
123 
124         assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT);
125         assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
126 
127         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_PORTRAIT);
128 
129         assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
130         assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
131     }
132 
133     @Test
testNotIgnoreOrientationRequest_onlyRespectsFocusedTaskDisplayArea()134     public void testNotIgnoreOrientationRequest_onlyRespectsFocusedTaskDisplayArea() {
135         mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
136         mSecondRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
137         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
138 
139         // Second TDA is not focused, so Display won't get the request
140         prepareUnresizable(mSecondActivity, SCREEN_ORIENTATION_LANDSCAPE);
141 
142         assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_UNSPECIFIED);
143 
144         // First TDA is focused, so Display gets the request
145         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LANDSCAPE);
146 
147         assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT);
148     }
149 
150     @Test
testIgnoreOrientationRequest_displayDoesNotReceiveOrientationChange()151     public void testIgnoreOrientationRequest_displayDoesNotReceiveOrientationChange() {
152         mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
153         mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
154         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
155 
156         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LANDSCAPE);
157 
158         verify(mFirstRoot).onDescendantOrientationChanged(any());
159         verify(mDisplay, never()).onDescendantOrientationChanged(any());
160     }
161 
162     @Test
testLaunchPortraitApp_fillsDisplayAreaGroup()163     public void testLaunchPortraitApp_fillsDisplayAreaGroup() {
164         mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
165         mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
166         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
167 
168         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_PORTRAIT);
169         final Rect dagBounds = new Rect(mFirstRoot.getBounds());
170         final Rect taskBounds = new Rect(mFirstTask.getBounds());
171         final Rect activityBounds = new Rect(mFirstActivity.getBounds());
172 
173         // DAG is portrait (860x1200), so Task and Activity fill DAG.
174         assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
175         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
176         assertThat(taskBounds).isEqualTo(dagBounds);
177         assertThat(activityBounds).isEqualTo(taskBounds);
178     }
179 
180     @Test
testLaunchPortraitApp_sizeCompatAfterRotation()181     public void testLaunchPortraitApp_sizeCompatAfterRotation() {
182         mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
183         mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
184         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
185 
186         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_PORTRAIT);
187         final Rect dagBounds = new Rect(mFirstRoot.getBounds());
188         final Rect activityBounds = new Rect(mFirstActivity.getBounds());
189 
190         rotateDisplay(mDisplay, ROTATION_90);
191         final Rect newDagBounds = new Rect(mFirstRoot.getBounds());
192         final Rect newTaskBounds = new Rect(mFirstTask.getBounds());
193         final Rect activitySizeCompatBounds = new Rect(mFirstActivity.getBounds());
194         final Rect activityConfigBounds =
195                 new Rect(mFirstActivity.getConfiguration().windowConfiguration.getBounds());
196 
197         // DAG is landscape (1200x860), no fixed orientation letterbox
198         assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
199         assertThat(mFirstActivity.inSizeCompatMode()).isTrue();
200         assertThat(newDagBounds.width()).isEqualTo(dagBounds.height());
201         assertThat(newDagBounds.height()).isEqualTo(dagBounds.width());
202         assertThat(newTaskBounds).isEqualTo(newDagBounds);
203 
204         // Activity config bounds is unchanged, size compat bounds is (860x[860x860/1200=616])
205         assertThat(mFirstActivity.getSizeCompatScale()).isLessThan(1f);
206         assertThat(activityConfigBounds.width()).isEqualTo(activityBounds.width());
207         assertThat(activityConfigBounds.height()).isEqualTo(activityBounds.height());
208         assertThat(activitySizeCompatBounds.height()).isEqualTo(newTaskBounds.height());
209         assertThat(activitySizeCompatBounds.width()).isEqualTo(
210                 newTaskBounds.height() * newTaskBounds.height() / newTaskBounds.width());
211     }
212 
213     @Test
testLaunchLandscapeApp_activityIsLetterboxForFixedOrientationInDisplayAreaGroup()214     public void testLaunchLandscapeApp_activityIsLetterboxForFixedOrientationInDisplayAreaGroup() {
215         mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
216         mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
217         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
218 
219         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LANDSCAPE);
220         final Rect dagBounds = new Rect(mFirstRoot.getBounds());
221         final Rect taskBounds = new Rect(mFirstTask.getBounds());
222         final Rect activityBounds = new Rect(mFirstActivity.getBounds());
223 
224         // DAG is portrait (860x1200), and activity is letterboxed for fixed orientation
225         // (860x[860x860/1200=616]). Task fills DAG.
226         assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isTrue();
227         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
228         assertThat(taskBounds).isEqualTo(dagBounds);
229         assertThat(activityBounds.width()).isEqualTo(dagBounds.width());
230         assertThat(activityBounds.height())
231                 .isEqualTo(dagBounds.width() * dagBounds.width() / dagBounds.height());
232     }
233 
234     @Test
testLaunchLandscapeApp_fixedOrientationLetterboxBecomesSizeCompatAfterRotation()235     public void testLaunchLandscapeApp_fixedOrientationLetterboxBecomesSizeCompatAfterRotation() {
236         mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
237         mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
238         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
239 
240         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LANDSCAPE);
241         final Rect dagBounds = new Rect(mFirstRoot.getBounds());
242         final Rect activityBounds = new Rect(mFirstActivity.getBounds());
243 
244         rotateDisplay(mDisplay, ROTATION_90);
245         final Rect newDagBounds = new Rect(mFirstRoot.getBounds());
246         final Rect newTaskBounds = new Rect(mFirstTask.getBounds());
247         final Rect newActivityBounds = new Rect(mFirstActivity.getBounds());
248 
249         // DAG is landscape (1200x860), no fixed orientation letterbox
250         assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
251         assertThat(mFirstActivity.inSizeCompatMode()).isTrue();
252         assertThat(newDagBounds.width()).isEqualTo(dagBounds.height());
253         assertThat(newDagBounds.height()).isEqualTo(dagBounds.width());
254         assertThat(newTaskBounds).isEqualTo(newDagBounds);
255 
256         // Because we don't scale up, there is no size compat bounds and app bounds is the same as
257         // the previous bounds.
258         assertThat(mFirstActivity.hasSizeCompatBounds()).isFalse();
259         assertThat(newActivityBounds.width()).isEqualTo(activityBounds.width());
260         assertThat(newActivityBounds.height()).isEqualTo(activityBounds.height());
261     }
262 
263     @Test
testPlaceImeContainer_reparentToTargetDisplayAreaGroup()264     public void testPlaceImeContainer_reparentToTargetDisplayAreaGroup() {
265         setupImeWindow();
266         final DisplayArea.Tokens imeContainer = mDisplay.getImeContainer();
267         final WindowToken imeToken = tokenOfType(TYPE_INPUT_METHOD);
268 
269         // By default, the ime container is attached to DC as defined in DAPolicy.
270         assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mDisplay);
271         assertThat(mDisplay.findAreaForTokenInLayer(imeToken)).isEqualTo(imeContainer);
272 
273         final WindowState firstActivityWin =
274                 createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity,
275                         "firstActivityWin");
276         spyOn(firstActivityWin);
277         final WindowState secondActivityWin =
278                 createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mSecondActivity,
279                         "firstActivityWin");
280         spyOn(secondActivityWin);
281 
282         // firstActivityWin should be the target
283         doReturn(true).when(firstActivityWin).canBeImeTarget();
284         doReturn(false).when(secondActivityWin).canBeImeTarget();
285 
286         WindowState imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */);
287 
288         assertThat(imeTarget).isEqualTo(firstActivityWin);
289         verify(mFirstRoot).placeImeContainer(imeContainer);
290         assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mFirstRoot);
291         assertThat(imeContainer.getParent().asDisplayArea().mFeatureId)
292                 .isEqualTo(FEATURE_IME_PLACEHOLDER);
293         assertThat(mDisplay.findAreaForTokenInLayer(imeToken)).isNull();
294         assertThat(mFirstRoot.findAreaForTokenInLayer(imeToken)).isEqualTo(imeContainer);
295         assertThat(mSecondRoot.findAreaForTokenInLayer(imeToken)).isNull();
296 
297         // secondActivityWin should be the target
298         doReturn(false).when(firstActivityWin).canBeImeTarget();
299         doReturn(true).when(secondActivityWin).canBeImeTarget();
300 
301         imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */);
302 
303         assertThat(imeTarget).isEqualTo(secondActivityWin);
304         verify(mSecondRoot).placeImeContainer(imeContainer);
305         assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondRoot);
306         assertThat(imeContainer.getParent().asDisplayArea().mFeatureId)
307                 .isEqualTo(FEATURE_IME_PLACEHOLDER);
308         assertThat(mDisplay.findAreaForTokenInLayer(imeToken)).isNull();
309         assertThat(mFirstRoot.findAreaForTokenInLayer(imeToken)).isNull();
310         assertThat(mSecondRoot.findAreaForTokenInLayer(imeToken)).isEqualTo(imeContainer);
311     }
312 
313     @Test
testPlaceImeContainer_hidesImeWhenParentChanges()314     public void testPlaceImeContainer_hidesImeWhenParentChanges() {
315         setupImeWindow();
316         final DisplayArea.Tokens imeContainer = mDisplay.getImeContainer();
317         final WindowToken imeToken = tokenOfType(TYPE_INPUT_METHOD);
318         final WindowState firstActivityWin =
319                 createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity,
320                         "firstActivityWin");
321         spyOn(firstActivityWin);
322         final WindowState secondActivityWin =
323                 createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mSecondActivity,
324                         "secondActivityWin");
325         spyOn(secondActivityWin);
326 
327         // firstActivityWin should be the target
328         doReturn(true).when(firstActivityWin).canBeImeTarget();
329         doReturn(false).when(secondActivityWin).canBeImeTarget();
330 
331         WindowState imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */);
332         assertThat(imeTarget).isEqualTo(firstActivityWin);
333         verify(mFirstRoot).placeImeContainer(imeContainer);
334 
335         // secondActivityWin should be the target
336         doReturn(false).when(firstActivityWin).canBeImeTarget();
337         doReturn(true).when(secondActivityWin).canBeImeTarget();
338 
339         spyOn(mDisplay.mInputMethodWindow);
340         imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */);
341 
342         assertThat(imeTarget).isEqualTo(secondActivityWin);
343         verify(mSecondRoot).placeImeContainer(imeContainer);
344         // verify hide() was called on InputMethodWindow.
345         verify(mDisplay.mInputMethodWindow).hide(false /* doAnimation */, false /* requestAnim */);
346     }
347 
348     @Test
testResizableFixedOrientationApp_fixedOrientationLetterboxing()349     public void testResizableFixedOrientationApp_fixedOrientationLetterboxing() {
350         mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
351         mSecondRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
352 
353         // Launch portrait on first DAG
354         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
355         prepareLimitedBounds(mFirstActivity, SCREEN_ORIENTATION_PORTRAIT,
356                 false /* isUnresizable */);
357 
358         // Display in landscape (as opposite to DAG), first DAG and activity in portrait
359         assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
360         assertThat(mFirstRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
361         assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
362         assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
363         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
364 
365         // Launch portrait on second DAG
366         mDisplay.onLastFocusedTaskDisplayAreaChanged(mSecondTda);
367         prepareLimitedBounds(mSecondActivity, SCREEN_ORIENTATION_LANDSCAPE,
368                 false /* isUnresizable */);
369 
370         // Display in portrait (as opposite to DAG), first DAG and activity in landscape
371         assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT);
372         assertThat(mSecondRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
373         assertThat(mSecondActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
374         assertThat(mSecondActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
375         assertThat(mSecondActivity.inSizeCompatMode()).isFalse();
376 
377         // First activity is letterboxed in portrait as requested.
378         assertThat(mFirstRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
379         assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
380         assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isTrue();
381         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
382 
383     }
384 
setupImeWindow()385     private void setupImeWindow() {
386         final WindowState imeWindow = createWindow(null /* parent */,
387                 TYPE_INPUT_METHOD, mDisplay, "mImeWindow");
388         imeWindow.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
389         mDisplay.mInputMethodWindow = imeWindow;
390     }
391 
tokenOfType(int type)392     private WindowToken tokenOfType(int type) {
393         return new WindowToken.Builder(mWm, new Binder(), type)
394                 .setDisplayContent(mDisplay).build();
395     }
396 
397     /** Display with two {@link DisplayAreaGroup}. Each of them take half of the screen. */
398     static class DualDisplayContent extends TestDisplayContent {
399         final DisplayAreaGroup mFirstRoot;
400         final DisplayAreaGroup mSecondRoot;
401         final Rect mLastDisplayBounds;
402 
403         /** Please use the {@link Builder} to create. */
DualDisplayContent(RootWindowContainer rootWindowContainer, Display display)404         DualDisplayContent(RootWindowContainer rootWindowContainer,
405                 Display display) {
406             super(rootWindowContainer, display);
407 
408             mFirstRoot = getGroupRoot(FEATURE_FIRST_ROOT);
409             mSecondRoot = getGroupRoot(FEATURE_SECOND_ROOT);
410             mLastDisplayBounds = new Rect(getBounds());
411             updateDisplayAreaGroupBounds();
412         }
413 
getGroupRoot(int rootFeatureId)414         DisplayAreaGroup getGroupRoot(int rootFeatureId) {
415             DisplayArea da = getDisplayArea(rootFeatureId);
416             assertThat(da).isInstanceOf(DisplayAreaGroup.class);
417             return (DisplayAreaGroup) da;
418         }
419 
getTaskDisplayArea(int tdaFeatureId)420         TaskDisplayArea getTaskDisplayArea(int tdaFeatureId) {
421             DisplayArea da = getDisplayArea(tdaFeatureId);
422             assertThat(da).isInstanceOf(TaskDisplayArea.class);
423             return (TaskDisplayArea) da;
424         }
425 
getDisplayArea(int featureId)426         DisplayArea getDisplayArea(int featureId) {
427             final DisplayArea displayArea =
428                     getItemFromDisplayAreas(da -> da.mFeatureId == featureId ? da : null);
429             assertThat(displayArea).isNotNull();
430             return displayArea;
431         }
432 
433         @Override
onConfigurationChanged(Configuration newParentConfig)434         public void onConfigurationChanged(Configuration newParentConfig) {
435             super.onConfigurationChanged(newParentConfig);
436 
437             final Rect curBounds = getBounds();
438             if (mLastDisplayBounds != null && !mLastDisplayBounds.equals(curBounds)) {
439                 mLastDisplayBounds.set(curBounds);
440                 updateDisplayAreaGroupBounds();
441             }
442         }
443 
444         /** Updates first and second {@link DisplayAreaGroup} to take half of the screen. */
updateDisplayAreaGroupBounds()445         private void updateDisplayAreaGroupBounds() {
446             if (mFirstRoot == null || mSecondRoot == null) {
447                 return;
448             }
449 
450             final Rect bounds = mLastDisplayBounds;
451             Rect groupBounds1, groupBounds2;
452             if (bounds.width() >= bounds.height()) {
453                 groupBounds1 = new Rect(bounds.left, bounds.top,
454                         (bounds.right + bounds.left) / 2, bounds.bottom);
455 
456                 groupBounds2 = new Rect((bounds.right + bounds.left) / 2, bounds.top,
457                         bounds.right, bounds.bottom);
458             } else {
459                 groupBounds1 = new Rect(bounds.left, bounds.top,
460                         bounds.right, (bounds.top + bounds.bottom) / 2);
461 
462                 groupBounds2 = new Rect(bounds.left,
463                         (bounds.top + bounds.bottom) / 2, bounds.right, bounds.bottom);
464             }
465             mFirstRoot.setBounds(groupBounds1);
466             mSecondRoot.setBounds(groupBounds2);
467         }
468 
469         static class Builder extends TestDisplayContent.Builder {
470 
Builder(ActivityTaskManagerService service, int width, int height)471             Builder(ActivityTaskManagerService service, int width, int height) {
472                 super(service, width, height);
473             }
474 
475             @Override
createInternal(Display display)476             TestDisplayContent createInternal(Display display) {
477                 return new DualDisplayContent(mService.mRootWindowContainer, display);
478             }
479 
build()480             DualDisplayContent build() {
481                 return (DualDisplayContent) super.build();
482             }
483         }
484     }
485 
486     /** Policy to create a dual {@link DisplayAreaGroup} policy in test. */
487     static class DualDisplayTestPolicyProvider implements DisplayAreaPolicy.Provider {
488 
489         @Override
instantiate(WindowManagerService wmService, DisplayContent content, RootDisplayArea root, DisplayArea.Tokens imeContainer)490         public DisplayAreaPolicy instantiate(WindowManagerService wmService, DisplayContent content,
491                 RootDisplayArea root, DisplayArea.Tokens imeContainer) {
492             // Root
493             // Include FEATURE_WINDOWED_MAGNIFICATION because it will be used as the screen rotation
494             // layer
495             DisplayAreaPolicyBuilder.HierarchyBuilder rootHierarchy =
496                     new DisplayAreaPolicyBuilder.HierarchyBuilder(root)
497                             .setImeContainer(imeContainer)
498                             .addFeature(new DisplayAreaPolicyBuilder.Feature.Builder(
499                                     wmService.mPolicy,
500                                     "WindowedMagnification", FEATURE_WINDOWED_MAGNIFICATION)
501                                     .upTo(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY)
502                                     .except(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY)
503                                     .setNewDisplayAreaSupplier(DisplayArea.Dimmable::new)
504                                     .build())
505                             .addFeature(new DisplayAreaPolicyBuilder.Feature.Builder(
506                                     wmService.mPolicy,
507                                     "ImePlaceholder", FEATURE_IME_PLACEHOLDER)
508                                     .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG)
509                                     .build());
510 
511             // First
512             final RootDisplayArea firstRoot = new DisplayAreaGroup(wmService, "FirstRoot",
513                     FEATURE_FIRST_ROOT);
514             final TaskDisplayArea firstTaskDisplayArea = new TaskDisplayArea(content, wmService,
515                     "FirstTaskDisplayArea", FEATURE_FIRST_TASK_CONTAINER);
516             final List<TaskDisplayArea> firstTdaList = new ArrayList<>();
517             firstTdaList.add(firstTaskDisplayArea);
518             DisplayAreaPolicyBuilder.HierarchyBuilder firstHierarchy =
519                     new DisplayAreaPolicyBuilder.HierarchyBuilder(firstRoot)
520                             .setTaskDisplayAreas(firstTdaList)
521                             .addFeature(new DisplayAreaPolicyBuilder.Feature.Builder(
522                                     wmService.mPolicy,
523                                     "ImePlaceholder", FEATURE_IME_PLACEHOLDER)
524                                     .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG)
525                                     .build());
526 
527             // Second
528             final RootDisplayArea secondRoot = new DisplayAreaGroup(wmService, "SecondRoot",
529                     FEATURE_SECOND_ROOT);
530             final TaskDisplayArea secondTaskDisplayArea = new TaskDisplayArea(content, wmService,
531                     "SecondTaskDisplayArea", FEATURE_SECOND_TASK_CONTAINER);
532             final List<TaskDisplayArea> secondTdaList = new ArrayList<>();
533             secondTdaList.add(secondTaskDisplayArea);
534             DisplayAreaPolicyBuilder.HierarchyBuilder secondHierarchy =
535                     new DisplayAreaPolicyBuilder.HierarchyBuilder(secondRoot)
536                             .setTaskDisplayAreas(secondTdaList)
537                             .addFeature(new DisplayAreaPolicyBuilder.Feature.Builder(
538                                     wmService.mPolicy,
539                                     "ImePlaceholder", FEATURE_IME_PLACEHOLDER)
540                                     .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG)
541                                     .build());
542 
543             return new DisplayAreaPolicyBuilder()
544                     .setRootHierarchy(rootHierarchy)
545                     .addDisplayAreaGroupHierarchy(firstHierarchy)
546                     .addDisplayAreaGroupHierarchy(secondHierarchy)
547                     .build(wmService);
548         }
549     }
550 }
551