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.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
20 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
21 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
22 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
23 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
24 import static android.view.WindowManager.LayoutParams.TYPE_POINTER;
25 import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
26 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
27 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
28 import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER;
29 import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
30 import static android.window.DisplayAreaOrganizer.FEATURE_FULLSCREEN_MAGNIFICATION;
31 import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER;
32 import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED;
33 import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED_BACKGROUND_PANEL;
34 import static android.window.DisplayAreaOrganizer.FEATURE_ROOT;
35 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
36 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_LAST;
37 import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
38 import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
39 
40 import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS;
41 import static com.android.server.wm.DisplayAreaPolicyBuilder.Feature;
42 
43 import static com.google.common.truth.Truth.assertThat;
44 
45 import static org.mockito.Mockito.doReturn;
46 import static org.mockito.Mockito.mock;
47 import static org.mockito.Mockito.when;
48 import static org.testng.Assert.assertThrows;
49 
50 import static java.util.stream.Collectors.toList;
51 
52 import android.content.res.Resources;
53 import android.os.Binder;
54 import android.os.Bundle;
55 import android.os.IBinder;
56 import android.platform.test.annotations.Presubmit;
57 import android.view.SurfaceControl;
58 
59 import com.google.android.collect.Lists;
60 
61 import org.hamcrest.CustomTypeSafeMatcher;
62 import org.hamcrest.Description;
63 import org.hamcrest.Matcher;
64 import org.junit.Before;
65 import org.junit.Rule;
66 import org.junit.Test;
67 
68 import java.util.ArrayList;
69 import java.util.HashMap;
70 import java.util.HashSet;
71 import java.util.List;
72 import java.util.Map;
73 import java.util.Set;
74 import java.util.function.Consumer;
75 import java.util.function.Function;
76 import java.util.stream.Collectors;
77 
78 /**
79  * Build/Install/Run:
80  *  atest WmTests:DisplayAreaPolicyBuilderTest
81  */
82 @Presubmit
83 public class DisplayAreaPolicyBuilderTest {
84 
85     @Rule
86     public final SystemServicesTestRule mSystemServices = new SystemServicesTestRule();
87 
88     private TestWindowManagerPolicy mPolicy = new TestWindowManagerPolicy(null, null);
89     private WindowManagerService mWms;
90     private RootDisplayArea mRoot;
91     private DisplayArea.Tokens mImeContainer;
92     private DisplayContent mDisplayContent;
93     private TaskDisplayArea mDefaultTaskDisplayArea;
94     private List<TaskDisplayArea> mTaskDisplayAreaList;
95     private RootDisplayArea mGroupRoot1;
96     private RootDisplayArea mGroupRoot2;
97     private TaskDisplayArea mTda1;
98     private TaskDisplayArea mTda2;
99 
100     @Before
setup()101     public void setup() {
102         mWms = mSystemServices.getWindowManagerService();
103         mRoot = new SurfacelessDisplayAreaRoot(mWms);
104         mImeContainer = new DisplayArea.Tokens(mWms, ABOVE_TASKS, "ImeContainer");
105         mDisplayContent = mock(DisplayContent.class);
106         doReturn(true).when(mDisplayContent).isTrusted();
107         mDisplayContent.isDefaultDisplay = true;
108         mDefaultTaskDisplayArea = new TaskDisplayArea(mDisplayContent, mWms, "Tasks",
109                 FEATURE_DEFAULT_TASK_CONTAINER);
110         mTaskDisplayAreaList = new ArrayList<>();
111         mTaskDisplayAreaList.add(mDefaultTaskDisplayArea);
112         mGroupRoot1 = new SurfacelessDisplayAreaRoot(mWms, "group1", FEATURE_VENDOR_FIRST + 1);
113         mGroupRoot2 = new SurfacelessDisplayAreaRoot(mWms, "group2", FEATURE_VENDOR_FIRST + 2);
114         mTda1 = new TaskDisplayArea(mDisplayContent, mWms, "tda1", FEATURE_VENDOR_FIRST + 3);
115         mTda2 = new TaskDisplayArea(mDisplayContent, mWms, "tda2", FEATURE_VENDOR_FIRST + 4);
116     }
117 
118     @Test
testBuilder()119     public void testBuilder() {
120         final Feature foo;
121         final Feature bar;
122         DisplayAreaPolicyBuilder.HierarchyBuilder rootHierarchy =
123                 new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
124                         .addFeature(foo = new Feature.Builder(mPolicy, "Foo",
125                                 FEATURE_VENDOR_FIRST)
126                                 .upTo(TYPE_STATUS_BAR)
127                                 .and(TYPE_NAVIGATION_BAR)
128                                 .build())
129                         .addFeature(bar = new Feature.Builder(mPolicy, "Bar",
130                                 FEATURE_VENDOR_FIRST + 1)
131                                 .all()
132                                 .except(TYPE_STATUS_BAR)
133                                 .build())
134                         .setImeContainer(mImeContainer)
135                         .setTaskDisplayAreas(mTaskDisplayAreaList);
136         DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder()
137                 .setRootHierarchy(rootHierarchy)
138                 .build(mWms);
139 
140         assertThat(policy.getDisplayAreas(foo.getId())).isNotEmpty();
141         assertThat(policy.getDisplayAreas(bar.getId())).isNotEmpty();
142 
143         Matcher<WindowContainer> fooDescendantMatcher = descendantOfOneOf(
144                 policy.getDisplayAreas(foo.getId()));
145         Matcher<WindowContainer> barDescendantMatcher = descendantOfOneOf(
146                 policy.getDisplayAreas(bar.getId()));
147 
148         // There is a DA of TYPE_STATUS_BAR below foo, but not below bar
149         assertThat(fooDescendantMatcher.matches(
150                 policy.findAreaForToken(tokenOfType(TYPE_STATUS_BAR)))).isTrue();
151         assertThat(barDescendantMatcher.matches(
152                 policy.findAreaForToken(tokenOfType(TYPE_STATUS_BAR)))).isFalse();
153 
154         // The TDA is below both foo and bar.
155         assertThat(fooDescendantMatcher.matches(mDefaultTaskDisplayArea)).isTrue();
156         assertThat(barDescendantMatcher.matches(mDefaultTaskDisplayArea)).isTrue();
157 
158         // The IME is below both foo and bar.
159         assertThat(fooDescendantMatcher.matches(mImeContainer)).isTrue();
160         assertThat(barDescendantMatcher.matches(mImeContainer)).isTrue();
161         assertThat(policy.findAreaForToken(tokenOfType(TYPE_INPUT_METHOD)))
162                 .isEqualTo(mImeContainer);
163         assertThat(policy.findAreaForToken(tokenOfType(TYPE_INPUT_METHOD_DIALOG)))
164                 .isEqualTo(mImeContainer);
165 
166         List<DisplayArea<?>> actualOrder = collectLeafAreas(mRoot);
167         Map<DisplayArea<?>, Set<Integer>> zSets = calculateZSets(policy, mImeContainer,
168                 mDefaultTaskDisplayArea);
169         actualOrder = actualOrder.stream().filter(zSets::containsKey).collect(toList());
170 
171         Map<DisplayArea<?>, Integer> expectedByMinLayer = mapValues(zSets,
172                 v -> v.stream().min(Integer::compareTo).get());
173         Map<DisplayArea<?>, Integer> expectedByMaxLayer = mapValues(zSets,
174                 v -> v.stream().max(Integer::compareTo).get());
175 
176         // Make sure the DAs' order is the same as their layer order.
177         assertMatchLayerOrder(actualOrder, expectedByMinLayer);
178         assertMatchLayerOrder(actualOrder, expectedByMaxLayer);
179     }
180 
181     @Test
testBuilder_defaultPolicy_hasOneHandedFeature()182     public void testBuilder_defaultPolicy_hasOneHandedFeature() {
183         final DisplayAreaPolicy.Provider defaultProvider = DisplayAreaPolicy.Provider.fromResources(
184                 resourcesWithProvider(""));
185         final DisplayAreaPolicyBuilder.Result defaultPolicy =
186                 (DisplayAreaPolicyBuilder.Result) defaultProvider.instantiate(mWms, mDisplayContent,
187                         mRoot, mImeContainer);
188         if (mDisplayContent.isDefaultDisplay) {
189             final List<Feature> features = defaultPolicy.getFeatures();
190             boolean hasOneHandedFeature = false;
191             for (Feature feature : features) {
192                 hasOneHandedFeature |= feature.getId() == FEATURE_ONE_HANDED;
193             }
194 
195             assertThat(hasOneHandedFeature).isTrue();
196         }
197     }
198 
199     @Test
testBuilder_defaultPolicy_hasOneHandedBackgroundFeature()200     public void testBuilder_defaultPolicy_hasOneHandedBackgroundFeature() {
201         final DisplayAreaPolicy.Provider defaultProvider = DisplayAreaPolicy.Provider.fromResources(
202                 resourcesWithProvider(""));
203         final DisplayAreaPolicyBuilder.Result defaultPolicy =
204                 (DisplayAreaPolicyBuilder.Result) defaultProvider.instantiate(mWms, mDisplayContent,
205                         mRoot, mImeContainer);
206         if (mDisplayContent.isDefaultDisplay) {
207             final List<Feature> features = defaultPolicy.getFeatures();
208             boolean hasOneHandedBackgroundFeature = false;
209             for (Feature feature : features) {
210                 hasOneHandedBackgroundFeature |=
211                         feature.getId() == FEATURE_ONE_HANDED_BACKGROUND_PANEL;
212             }
213 
214             assertThat(hasOneHandedBackgroundFeature).isTrue();
215         }
216     }
217 
218     @Test
testBuilder_defaultPolicy_hasWindowedMagnificationFeature()219     public void testBuilder_defaultPolicy_hasWindowedMagnificationFeature() {
220         final DisplayAreaPolicy.Provider defaultProvider = DisplayAreaPolicy.Provider.fromResources(
221                 resourcesWithProvider(""));
222         final DisplayAreaPolicyBuilder.Result defaultPolicy =
223                 (DisplayAreaPolicyBuilder.Result) defaultProvider.instantiate(mWms, mDisplayContent,
224                         mRoot, mImeContainer);
225         final List<Feature> features = defaultPolicy.getFeatures();
226         boolean hasWindowedMagnificationFeature = false;
227         for (Feature feature : features) {
228             hasWindowedMagnificationFeature |= feature.getId() == FEATURE_WINDOWED_MAGNIFICATION;
229         }
230 
231         assertThat(hasWindowedMagnificationFeature).isTrue();
232     }
233 
234     @Test
testBuilder_defaultPolicy_hasFullscreenMagnificationFeature()235     public void testBuilder_defaultPolicy_hasFullscreenMagnificationFeature() {
236         final DisplayAreaPolicy.Provider defaultProvider = DisplayAreaPolicy.Provider.fromResources(
237                 resourcesWithProvider(""));
238         final DisplayAreaPolicyBuilder.Result defaultPolicy =
239                 (DisplayAreaPolicyBuilder.Result) defaultProvider.instantiate(mWms, mDisplayContent,
240                         mRoot, mImeContainer);
241         final List<Feature> features = defaultPolicy.getFeatures();
242         boolean hasFullscreenMagnificationFeature = false;
243         for (Feature feature : features) {
244             hasFullscreenMagnificationFeature |=
245                     feature.getId() == FEATURE_FULLSCREEN_MAGNIFICATION;
246         }
247 
248         assertThat(hasFullscreenMagnificationFeature).isTrue();
249     }
250 
251     @Test
testBuilder_defaultPolicy_hasImePlaceholderFeature()252     public void testBuilder_defaultPolicy_hasImePlaceholderFeature() {
253         final DisplayAreaPolicy.Provider defaultProvider = DisplayAreaPolicy.Provider.fromResources(
254                 resourcesWithProvider(""));
255         final DisplayAreaPolicyBuilder.Result defaultPolicy =
256                 (DisplayAreaPolicyBuilder.Result) defaultProvider.instantiate(mWms, mDisplayContent,
257                         mRoot, mImeContainer);
258         final List<Feature> features = defaultPolicy.getFeatures();
259         boolean hasImePlaceholderFeature = false;
260         for (Feature feature : features) {
261             hasImePlaceholderFeature |= feature.getId() == FEATURE_IME_PLACEHOLDER;
262         }
263 
264         assertThat(hasImePlaceholderFeature).isTrue();
265     }
266 
267     @Test
testBuilder_createCustomizedDisplayAreaForFeature()268     public void testBuilder_createCustomizedDisplayAreaForFeature() {
269         final Feature dimmable;
270         final Feature other;
271         DisplayAreaPolicyBuilder.HierarchyBuilder rootHierarchy =
272                 new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
273                         .addFeature(dimmable = new Feature.Builder(mPolicy, "Dimmable",
274                                 FEATURE_VENDOR_FIRST)
275                                 .upTo(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY)
276                                 .except(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY)
277                                 .setNewDisplayAreaSupplier(DisplayArea.Dimmable::new)
278                                 .build())
279                         .addFeature(other = new Feature.Builder(mPolicy, "Other",
280                                 FEATURE_VENDOR_FIRST + 1)
281                                 .all()
282                                 .build())
283                         .setImeContainer(mImeContainer)
284                         .setTaskDisplayAreas(mTaskDisplayAreaList);
285         DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder()
286                 .setRootHierarchy(rootHierarchy)
287                 .build(mWms);
288 
289         List<DisplayArea<? extends WindowContainer>> dimmableDAs =
290                 policy.getDisplayAreas(dimmable.getId());
291         List<DisplayArea<? extends WindowContainer>> otherDAs =
292                 policy.getDisplayAreas(other.getId());
293         assertThat(dimmableDAs).hasSize(1);
294         assertThat(dimmableDAs.get(0)).isInstanceOf(DisplayArea.Dimmable.class);
295         for (DisplayArea otherDA : otherDAs) {
296             assertThat(otherDA).isNotInstanceOf(DisplayArea.Dimmable.class);
297         }
298     }
299 
300     @Test
testBuilder_singleRoot_validateSettings()301     public void testBuilder_singleRoot_validateSettings() {
302         final DisplayAreaPolicyBuilder builder = new DisplayAreaPolicyBuilder();
303 
304         // Root must be set.
305         assertThrows(IllegalStateException.class, () -> builder.build(mWms));
306 
307         // IME must be set.
308         builder.setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
309                 .setTaskDisplayAreas(mTaskDisplayAreaList));
310 
311         assertThrows(IllegalStateException.class, () -> builder.build(mWms));
312 
313         // Default TaskDisplayArea must be set.
314         builder.setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
315                 .setImeContainer(mImeContainer)
316                 .setTaskDisplayAreas(Lists.newArrayList(
317                         new TaskDisplayArea(mDisplayContent, mWms, "testTda",
318                                 FEATURE_VENDOR_FIRST + 1))));
319 
320         assertThrows(IllegalStateException.class, () -> builder.build(mWms));
321 
322         // No exception
323         builder.setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
324                 .setImeContainer(mImeContainer)
325                 .setTaskDisplayAreas(mTaskDisplayAreaList));
326 
327         builder.build(mWms);
328     }
329 
330     @Test
testBuilder_displayAreaGroup_validateSettings()331     public void testBuilder_displayAreaGroup_validateSettings() {
332         final DisplayAreaPolicyBuilder builder1 = new DisplayAreaPolicyBuilder();
333 
334         // IME must be set to one of the roots.
335         builder1.setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot));
336         builder1.addDisplayAreaGroupHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(
337                 mGroupRoot1)
338                 .setTaskDisplayAreas(mTaskDisplayAreaList));
339 
340         assertThrows(IllegalStateException.class, () -> builder1.build(mWms));
341 
342         // Default TaskDisplayArea must be set.
343         final DisplayAreaPolicyBuilder builder2 = new DisplayAreaPolicyBuilder();
344         builder2.setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot));
345         builder2.addDisplayAreaGroupHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(
346                 mGroupRoot1)
347                 .setImeContainer(mImeContainer)
348                 .setTaskDisplayAreas(Lists.newArrayList(mTda1)));
349 
350         assertThrows(IllegalStateException.class, () -> builder2.build(mWms));
351 
352         // Each DisplayAreaGroup must have at least one TaskDisplayArea.
353         final DisplayAreaPolicyBuilder builder3 = new DisplayAreaPolicyBuilder();
354         builder3.setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot));
355         builder3.addDisplayAreaGroupHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(
356                 mGroupRoot1)
357                 .setImeContainer(mImeContainer)
358                 .setTaskDisplayAreas(mTaskDisplayAreaList));
359         builder3.addDisplayAreaGroupHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(
360                 mGroupRoot2));
361 
362         assertThrows(IllegalStateException.class, () -> builder3.build(mWms));
363 
364         // No exception
365         final DisplayAreaPolicyBuilder builder4 = new DisplayAreaPolicyBuilder();
366         builder4.setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot));
367         builder4.addDisplayAreaGroupHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(
368                 mGroupRoot1)
369                 .setImeContainer(mImeContainer)
370                 .setTaskDisplayAreas(mTaskDisplayAreaList));
371         builder4.addDisplayAreaGroupHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(
372                 mGroupRoot2)
373                 .setTaskDisplayAreas(Lists.newArrayList(mTda1)));
374 
375         builder4.build(mWms);
376     }
377 
378     @Test
testBuilder_rootHasUniqueId()379     public void testBuilder_rootHasUniqueId() {
380         // Root must have different id from all roots.
381         final DisplayAreaPolicyBuilder builder1 = new DisplayAreaPolicyBuilder();
382         builder1.setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
383                 .setImeContainer(mImeContainer)
384                 .setTaskDisplayAreas(mTaskDisplayAreaList));
385         final RootDisplayArea groupRoot1 = new SurfacelessDisplayAreaRoot(mWms, "group1",
386                 mRoot.mFeatureId);
387         builder1.addDisplayAreaGroupHierarchy(
388                 new DisplayAreaPolicyBuilder.HierarchyBuilder(groupRoot1)
389                         .setTaskDisplayAreas(Lists.newArrayList(mTda1)));
390 
391         assertThrows(IllegalStateException.class, () -> builder1.build(mWms));
392 
393         // Root must have different id from all TDAs.
394         final DisplayAreaPolicyBuilder builder2 = new DisplayAreaPolicyBuilder();
395         builder2.setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
396                 .setImeContainer(mImeContainer)
397                 .setTaskDisplayAreas(Lists.newArrayList(
398                         mDefaultTaskDisplayArea,
399                         new TaskDisplayArea(mDisplayContent, mWms, "testTda",
400                                 mRoot.mFeatureId))));
401 
402         assertThrows(IllegalStateException.class, () -> builder2.build(mWms));
403 
404         // Root must have different id from all features.
405         final DisplayAreaPolicyBuilder builder3 = new DisplayAreaPolicyBuilder();
406         builder3.setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
407                 .setImeContainer(mImeContainer)
408                 .setTaskDisplayAreas(mTaskDisplayAreaList)
409                 .addFeature(new Feature.Builder(mPolicy, "testFeature", mRoot.mFeatureId)
410                         .all()
411                         .build()));
412 
413         assertThrows(IllegalStateException.class, () -> builder3.build(mWms));
414     }
415 
416     @Test
testBuilder_taskDisplayAreaHasUniqueId()417     public void testBuilder_taskDisplayAreaHasUniqueId() {
418         // TDA must have different id from all TDAs.
419         final DisplayAreaPolicyBuilder builder = new DisplayAreaPolicyBuilder();
420         final List<TaskDisplayArea> tdaList = Lists.newArrayList(
421                 mDefaultTaskDisplayArea,
422                 mTda1,
423                 new TaskDisplayArea(mDisplayContent, mWms, "tda2", mTda1.mFeatureId));
424         builder.setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
425                 .setImeContainer(mImeContainer)
426                 .setTaskDisplayAreas(tdaList));
427 
428         assertThrows(IllegalStateException.class, () -> builder.build(mWms));
429 
430         // TDA must have different id from all features.
431         final DisplayAreaPolicyBuilder builder2 = new DisplayAreaPolicyBuilder();
432         builder2.setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
433                 .setImeContainer(mImeContainer)
434                 .setTaskDisplayAreas(Lists.newArrayList(
435                         mDefaultTaskDisplayArea,
436                         mTda1))
437                 .addFeature(new Feature.Builder(mPolicy, "testFeature", mTda1.mFeatureId)
438                         .all()
439                         .build()));
440 
441         assertThrows(IllegalStateException.class, () -> builder2.build(mWms));
442     }
443 
444     @Test
testBuilder_featureHasUniqueId()445     public void testBuilder_featureHasUniqueId() {
446         // Feature must have different id from features below the same root.
447         final DisplayAreaPolicyBuilder builder = new DisplayAreaPolicyBuilder();
448         builder.setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
449                 .setImeContainer(mImeContainer)
450                 .setTaskDisplayAreas(mTaskDisplayAreaList)
451                 .addFeature(new Feature.Builder(mPolicy, "feature1", FEATURE_VENDOR_FIRST + 10)
452                         .all()
453                         .build())
454                 .addFeature(new Feature.Builder(mPolicy, "feature2", FEATURE_VENDOR_FIRST + 10)
455                         .upTo(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY)
456                         .build()));
457 
458         assertThrows(IllegalStateException.class, () -> builder.build(mWms));
459 
460         // Features below different root can have the same id.
461         final DisplayAreaPolicyBuilder builder2 = new DisplayAreaPolicyBuilder();
462         builder2.setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
463                 .setImeContainer(mImeContainer)
464                 .setTaskDisplayAreas(mTaskDisplayAreaList)
465                 .addFeature(new Feature.Builder(mPolicy, "feature1", FEATURE_VENDOR_FIRST + 10)
466                         .all()
467                         .build()));
468         builder2.addDisplayAreaGroupHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(
469                 mGroupRoot1)
470                 .setTaskDisplayAreas(Lists.newArrayList(mTda1))
471                 .addFeature(new Feature.Builder(mPolicy, "feature2", FEATURE_VENDOR_FIRST + 10)
472                         .all()
473                         .build()));
474 
475         builder2.build(mWms);
476     }
477 
478     @Test
testBuilder_idsNotGreaterThanFeatureVendorLast()479     public void testBuilder_idsNotGreaterThanFeatureVendorLast() {
480         // Root id should not be greater than FEATURE_VENDOR_LAST.
481         final DisplayAreaPolicyBuilder builder1 = new DisplayAreaPolicyBuilder();
482         final RootDisplayArea root = new SurfacelessDisplayAreaRoot(mWms, "testRoot",
483                 FEATURE_VENDOR_LAST + 1);
484         builder1.setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(root)
485                 .setImeContainer(mImeContainer)
486                 .setTaskDisplayAreas(mTaskDisplayAreaList));
487 
488         assertThrows(IllegalStateException.class, () -> builder1.build(mWms));
489 
490         // TDA id should not be greater than FEATURE_VENDOR_LAST.
491         final DisplayAreaPolicyBuilder builder2 = new DisplayAreaPolicyBuilder();
492         builder2.setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(root)
493                 .setImeContainer(mImeContainer)
494                 .setTaskDisplayAreas(Lists.newArrayList(
495                         mDefaultTaskDisplayArea,
496                         new TaskDisplayArea(mDisplayContent, mWms, "testTda",
497                                 FEATURE_VENDOR_LAST + 1))));
498 
499         assertThrows(IllegalStateException.class, () -> builder2.build(mWms));
500 
501         // Feature id should not be greater than FEATURE_VENDOR_LAST.
502         final DisplayAreaPolicyBuilder builder3 = new DisplayAreaPolicyBuilder();
503         builder3.setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
504                 .setImeContainer(mImeContainer)
505                 .setTaskDisplayAreas(mTaskDisplayAreaList)
506                 .addFeature(new Feature.Builder(mPolicy, "testFeature", FEATURE_VENDOR_LAST + 1)
507                         .all()
508                         .build()));
509 
510         assertThrows(IllegalStateException.class, () -> builder3.build(mWms));
511     }
512 
513     @Test
testBuilder_displayAreaGroup_attachDisplayAreas()514     public void testBuilder_displayAreaGroup_attachDisplayAreas() {
515         final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder()
516                 .setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
517                         .setTaskDisplayAreas(mTaskDisplayAreaList))
518                 .addDisplayAreaGroupHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(
519                         mGroupRoot1)
520                         .setImeContainer(mImeContainer)
521                         .setTaskDisplayAreas(Lists.newArrayList(mTda1)))
522                 .addDisplayAreaGroupHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(
523                         mGroupRoot2)
524                         .setTaskDisplayAreas(Lists.newArrayList(mTda2)))
525                 .build(mWms);
526 
527         assertThat(mDefaultTaskDisplayArea.isDescendantOf(mRoot)).isTrue();
528         assertThat(mGroupRoot1.isDescendantOf(mRoot)).isTrue();
529         assertThat(mGroupRoot2.isDescendantOf(mRoot)).isTrue();
530         assertThat(mImeContainer.isDescendantOf(mGroupRoot1)).isTrue();
531         assertThat(mTda1.isDescendantOf(mGroupRoot1)).isTrue();
532         assertThat(mTda2.isDescendantOf(mGroupRoot2)).isTrue();
533         assertThat(isSibling(mDefaultTaskDisplayArea, mGroupRoot1)).isTrue();
534         assertThat(isSibling(mDefaultTaskDisplayArea, mGroupRoot2)).isTrue();
535     }
536 
537     @Test
testBuilder_displayAreaGroup_createFeatureOnGroup()538     public void testBuilder_displayAreaGroup_createFeatureOnGroup() {
539         final Feature feature1 = new Feature.Builder(mWms.mPolicy, "feature1",
540                 FEATURE_VENDOR_FIRST + 5)
541                 .all()
542                 .except(TYPE_STATUS_BAR)
543                 .build();
544         final Feature feature2 = new Feature.Builder(mWms.mPolicy, "feature2",
545                 FEATURE_VENDOR_FIRST + 6)
546                 .upTo(TYPE_STATUS_BAR)
547                 .and(TYPE_NAVIGATION_BAR)
548                 .build();
549         final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder()
550                 .setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
551                         .setTaskDisplayAreas(mTaskDisplayAreaList))
552                 .addDisplayAreaGroupHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(
553                         mGroupRoot1)
554                         .setImeContainer(mImeContainer)
555                         .setTaskDisplayAreas(Lists.newArrayList(mTda1))
556                         .addFeature(feature1))
557                 .addDisplayAreaGroupHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(
558                         mGroupRoot2)
559                         .setTaskDisplayAreas(Lists.newArrayList(mTda2))
560                         .addFeature(feature2))
561                 .build(mWms);
562 
563         List<DisplayArea<? extends WindowContainer>> feature1DAs =
564                 policy.getDisplayAreas(feature1.getId());
565         List<DisplayArea<? extends WindowContainer>> feature2DAs =
566                 policy.getDisplayAreas(feature2.getId());
567         for (DisplayArea<? extends WindowContainer> da : feature1DAs) {
568             assertThat(da.isDescendantOf(mGroupRoot1)).isTrue();
569         }
570         for (DisplayArea<? extends WindowContainer> da : feature2DAs) {
571             assertThat(da.isDescendantOf(mGroupRoot2)).isTrue();
572         }
573     }
574 
575     @Test
testBuilder_addWindow_selectContainerForWindowFunc_defaultFunc()576     public void testBuilder_addWindow_selectContainerForWindowFunc_defaultFunc() {
577         final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder()
578                 .setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
579                         .setTaskDisplayAreas(mTaskDisplayAreaList))
580                 .addDisplayAreaGroupHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(
581                         mGroupRoot1)
582                         .setImeContainer(mImeContainer)
583                         .setTaskDisplayAreas(Lists.newArrayList(mTda1)))
584                 .addDisplayAreaGroupHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(
585                         mGroupRoot2)
586                         .setTaskDisplayAreas(Lists.newArrayList(mTda2)))
587                 .build(mWms);
588 
589         final WindowToken token = new WindowToken.Builder(mWms, mock(IBinder.class),
590                 TYPE_STATUS_BAR)
591                 .setDisplayContent(mDisplayContent)
592                 .setPersistOnEmpty(true)
593                 .setOwnerCanManageAppTokens(true)
594                 .build();
595 
596         policy.addWindow(token);
597 
598         // By default, window are always added to the root.
599         assertThat(token.isDescendantOf(mRoot)).isTrue();
600         assertThat(token.isDescendantOf(mGroupRoot1)).isFalse();
601         assertThat(token.isDescendantOf(mGroupRoot2)).isFalse();
602 
603         // When the window has options for target root id, attach it to the target root.
604         final Bundle options = new Bundle();
605         options.putInt(KEY_ROOT_DISPLAY_AREA_ID, mGroupRoot2.mFeatureId);
606         final WindowToken token2 = new WindowToken.Builder(mWms, mock(IBinder.class),
607                 TYPE_STATUS_BAR)
608                 .setDisplayContent(mDisplayContent)
609                 .setPersistOnEmpty(true)
610                 .setOwnerCanManageAppTokens(true)
611                 .setOptions(options)
612                 .build();
613         policy.addWindow(token2);
614 
615         assertThat(token2.isDescendantOf(mGroupRoot2)).isTrue();
616     }
617 
618     @Test
testBuilder_addWindow_selectContainerForWindowFunc_selectBasedOnType()619     public void testBuilder_addWindow_selectContainerForWindowFunc_selectBasedOnType() {
620         final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy1 =
621                 new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot1)
622                         .setImeContainer(mImeContainer)
623                         .setTaskDisplayAreas(Lists.newArrayList(mTda1));
624         final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy2 =
625                 new DisplayAreaPolicyBuilder.HierarchyBuilder(mGroupRoot2)
626                         .setTaskDisplayAreas(Lists.newArrayList(mTda2));
627         final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder()
628                 .setRootHierarchy(new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
629                         .setTaskDisplayAreas(mTaskDisplayAreaList))
630                 .addDisplayAreaGroupHierarchy(hierarchy1)
631                 .addDisplayAreaGroupHierarchy(hierarchy2)
632                 .setSelectRootForWindowFunc((type, options) -> {
633                     if (type == TYPE_STATUS_BAR) {
634                         return mGroupRoot1;
635                     }
636                     return mGroupRoot2;
637                 })
638                 .build(mWms);
639 
640         final WindowToken token1 = new WindowToken.Builder(mWms, mock(IBinder.class),
641                 TYPE_STATUS_BAR)
642                 .setDisplayContent(mDisplayContent)
643                 .setPersistOnEmpty(true)
644                 .setOwnerCanManageAppTokens(true)
645                 .build();
646         final WindowToken token2 = new WindowToken.Builder(mWms, mock(IBinder.class),
647                 TYPE_WALLPAPER)
648                 .setDisplayContent(mDisplayContent)
649                 .setPersistOnEmpty(true)
650                 .setOwnerCanManageAppTokens(true)
651                 .build();
652         policy.addWindow(token1);
653         policy.addWindow(token2);
654 
655         assertThat(token1.isDescendantOf(mGroupRoot1)).isTrue();
656         assertThat(token2.isDescendantOf(mGroupRoot2)).isTrue();
657     }
658 
659     @Test
testBuilder_addWindow_selectContainerForWindowFunc_selectBasedOnOptions()660     public void testBuilder_addWindow_selectContainerForWindowFunc_selectBasedOnOptions() {
661         final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy0 =
662                 new DisplayAreaPolicyBuilder.HierarchyBuilder(mRoot)
663                         .setTaskDisplayAreas(mTaskDisplayAreaList);
664         final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy1 =
665                 new DisplayAreaPolicyBuilder.HierarchyBuilder(
666                         mGroupRoot1)
667                         .setImeContainer(mImeContainer)
668                         .setTaskDisplayAreas(Lists.newArrayList(mTda1));
669         final DisplayAreaPolicyBuilder.HierarchyBuilder hierarchy2 =
670                 new DisplayAreaPolicyBuilder.HierarchyBuilder(
671                         mGroupRoot2)
672                         .setTaskDisplayAreas(Lists.newArrayList(mTda2));
673         final DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder()
674                 .setRootHierarchy(hierarchy0)
675                 .addDisplayAreaGroupHierarchy(hierarchy1)
676                 .addDisplayAreaGroupHierarchy(hierarchy2)
677                 .setSelectRootForWindowFunc((token, options) -> {
678                     if (options == null) {
679                         return mRoot;
680                     }
681                     if (options.getInt("HIERARCHY_ROOT_ID") == mGroupRoot1.mFeatureId) {
682                         return mGroupRoot1;
683                     }
684                     if (options.getInt("HIERARCHY_ROOT_ID") == mGroupRoot2.mFeatureId) {
685                         return mGroupRoot2;
686                     }
687                     return mRoot;
688                 })
689                 .build(mWms);
690 
691         final Bundle options1 = new Bundle();
692         options1.putInt("HIERARCHY_ROOT_ID", mGroupRoot1.mFeatureId);
693         final Bundle options2 = new Bundle();
694         options2.putInt("HIERARCHY_ROOT_ID", mGroupRoot2.mFeatureId);
695         final WindowToken token0 = new WindowToken.Builder(mWms, mock(IBinder.class),
696                 TYPE_STATUS_BAR)
697                 .setDisplayContent(mDisplayContent)
698                 .setPersistOnEmpty(true)
699                 .setOwnerCanManageAppTokens(true)
700                 .build();
701         final WindowToken token1 = new WindowToken.Builder(mWms, mock(IBinder.class),
702                 TYPE_STATUS_BAR)
703                 .setDisplayContent(mDisplayContent)
704                 .setPersistOnEmpty(true)
705                 .setOwnerCanManageAppTokens(true)
706                 .setOptions(options1)
707                 .build();
708         final WindowToken token2 = new WindowToken.Builder(mWms, mock(IBinder.class),
709                 TYPE_STATUS_BAR)
710                 .setDisplayContent(mDisplayContent)
711                 .setPersistOnEmpty(true)
712                 .setOwnerCanManageAppTokens(true)
713                 .setOptions(options2)
714                 .build();
715 
716         policy.addWindow(token0);
717         policy.addWindow(token1);
718         policy.addWindow(token2);
719 
720         assertThat(token0.isDescendantOf(mRoot)).isTrue();
721         assertThat(token1.isDescendantOf(mGroupRoot1)).isTrue();
722         assertThat(token2.isDescendantOf(mGroupRoot2)).isTrue();
723     }
724 
725     @Test
testFeatureNotThrowArrayIndexOutOfBoundsException()726     public void testFeatureNotThrowArrayIndexOutOfBoundsException() {
727         final Feature feature1 = new Feature.Builder(mWms.mPolicy, "feature1",
728                 FEATURE_VENDOR_FIRST + 5)
729                 .all()
730                 .except(TYPE_POINTER)
731                 .build();
732     }
733 
resourcesWithProvider(String provider)734     private static Resources resourcesWithProvider(String provider) {
735         Resources mock = mock(Resources.class);
736         when(mock.getString(
737                 com.android.internal.R.string.config_deviceSpecificDisplayAreaPolicyProvider))
738                 .thenReturn(provider);
739         return mock;
740     }
741 
mapValues(Map<K, V> zSets, Function<V, R> f)742     private <K, V, R> Map<K, R> mapValues(Map<K, V> zSets, Function<V, R> f) {
743         return zSets.entrySet().stream().collect(Collectors.toMap(
744                 Map.Entry::getKey,
745                 e -> f.apply(e.getValue())));
746     }
747 
collectLeafAreas(DisplayArea<?> root)748     private List<DisplayArea<?>> collectLeafAreas(DisplayArea<?> root) {
749         ArrayList<DisplayArea<?>> leafs = new ArrayList<>();
750         traverseLeafAreas(root, leafs::add);
751         return leafs;
752     }
753 
calculateZSets( DisplayAreaPolicyBuilder.Result policy, DisplayArea.Tokens ime, TaskDisplayArea taskDisplayArea)754     private Map<DisplayArea<?>, Set<Integer>> calculateZSets(
755             DisplayAreaPolicyBuilder.Result policy,
756             DisplayArea.Tokens ime,
757             TaskDisplayArea taskDisplayArea) {
758         Map<DisplayArea<?>, Set<Integer>> zSets = new HashMap<>();
759         int[] types = {TYPE_STATUS_BAR, TYPE_NAVIGATION_BAR, TYPE_PRESENTATION,
760                 TYPE_APPLICATION_OVERLAY};
761         for (int type : types) {
762             WindowToken token = tokenOfType(type);
763             recordLayer(policy.findAreaForToken(token), token.getWindowLayerFromType(), zSets);
764         }
765         recordLayer(taskDisplayArea, APPLICATION_LAYER, zSets);
766         recordLayer(ime, mPolicy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD), zSets);
767         return zSets;
768     }
769 
recordLayer(DisplayArea<?> area, int layer, Map<DisplayArea<?>, Set<Integer>> zSets)770     private void recordLayer(DisplayArea<?> area, int layer,
771             Map<DisplayArea<?>,  Set<Integer>> zSets) {
772         zSets.computeIfAbsent(area, k -> new HashSet<>()).add(layer);
773     }
774 
descendantOfOneOf(List<? extends WindowContainer> expected)775     private Matcher<WindowContainer> descendantOfOneOf(List<? extends WindowContainer> expected) {
776         return new CustomTypeSafeMatcher<WindowContainer>("descendant of one of " + expected) {
777             @Override
778             protected boolean matchesSafely(WindowContainer actual) {
779                 for (WindowContainer expected : expected) {
780                     WindowContainer candidate = actual;
781                     while (candidate != null && candidate.getParent() != candidate) {
782                         if (candidate.getParent() == expected) {
783                             return true;
784                         }
785                         candidate = candidate.getParent();
786                     }
787                 }
788                 return false;
789             }
790 
791             @Override
792             protected void describeMismatchSafely(WindowContainer item,
793                     Description description) {
794                 description.appendText("was ").appendValue(item);
795                 while (item != null && item.getParent() != item) {
796                     item = item.getParent();
797                     description.appendText(", child of ").appendValue(item);
798                 }
799             }
800         };
801     }
802 
803     private boolean isSibling(WindowContainer da1, WindowContainer da2) {
804         return da1.getParent() != null && da1.getParent() == da2.getParent();
805     }
806 
807     private WindowToken tokenOfType(int type) {
808         return new WindowToken.Builder(mWms, new Binder(), type)
809                 .setDisplayContent(mDisplayContent).build();
810     }
811 
812     private static void assertMatchLayerOrder(List<DisplayArea<?>> actualOrder,
813             Map<DisplayArea<?>, Integer> areaToLayerMap) {
814         for (int i = 0; i < actualOrder.size() - 1; i++) {
815             DisplayArea<?> curr = actualOrder.get(i);
816             DisplayArea<?> next = actualOrder.get(i + 1);
817             assertThat(areaToLayerMap.get(curr)).isLessThan(areaToLayerMap.get(next));
818         }
819     }
820 
821     private static void traverseLeafAreas(DisplayArea<?> root, Consumer<DisplayArea<?>> consumer) {
822         boolean leaf = true;
823         for (int i = 0; i < root.getChildCount(); i++) {
824             WindowContainer child = root.getChildAt(i);
825             if (child instanceof DisplayArea<?>) {
826                 traverseLeafAreas((DisplayArea<?>) child, consumer);
827                 leaf = false;
828             }
829         }
830         if (leaf) {
831             consumer.accept(root);
832         }
833     }
834 
835     static class SurfacelessDisplayAreaRoot extends RootDisplayArea {
836 
837         SurfacelessDisplayAreaRoot(WindowManagerService wms) {
838             this(wms, "SurfacelessDisplayAreaRoot", FEATURE_ROOT);
839         }
840 
841         SurfacelessDisplayAreaRoot(WindowManagerService wms, String name, int featureId) {
842             super(wms, name, featureId);
843         }
844 
845         @Override
846         SurfaceControl.Builder makeChildSurface(WindowContainer child) {
847             return new MockSurfaceControlBuilder();
848         }
849     }
850 
851 }
852