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