1 /*
2  * Copyright (C) 2017 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.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
22 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
23 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
24 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
25 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
26 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
27 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
28 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
29 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
30 import static android.content.res.Configuration.EMPTY;
31 import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
32 
33 import static org.junit.Assert.assertEquals;
34 import static org.junit.Assert.assertTrue;
35 
36 import android.content.res.Configuration;
37 import android.graphics.Rect;
38 import android.platform.test.annotations.Presubmit;
39 
40 import androidx.test.filters.SmallTest;
41 
42 import org.junit.Test;
43 
44 import java.util.ArrayList;
45 import java.util.List;
46 
47 /**
48  * Test class for {@link ConfigurationContainer}.
49  *
50  * Build/Install/Run:
51  *  atest WmTests:ConfigurationContainerTests
52  */
53 @SmallTest
54 @Presubmit
55 public class ConfigurationContainerTests {
56 
57     @Test
testConfigurationInit()58     public void testConfigurationInit() {
59         // Check root container initial config.
60         final TestConfigurationContainer root = new TestConfigurationContainer();
61         assertEquals(EMPTY, root.getRequestedOverrideConfiguration());
62         assertEquals(EMPTY, root.getMergedOverrideConfiguration());
63         assertEquals(EMPTY, root.getConfiguration());
64 
65         // Check child initial config.
66         final TestConfigurationContainer child1 = root.addChild();
67         assertEquals(EMPTY, child1.getRequestedOverrideConfiguration());
68         assertEquals(EMPTY, child1.getMergedOverrideConfiguration());
69         assertEquals(EMPTY, child1.getConfiguration());
70 
71         // Check child initial config if root has overrides.
72         final Configuration rootOverrideConfig = new Configuration();
73         rootOverrideConfig.fontScale = 1.3f;
74         root.onRequestedOverrideConfigurationChanged(rootOverrideConfig);
75         final TestConfigurationContainer child2 = root.addChild();
76         assertEquals(EMPTY, child2.getRequestedOverrideConfiguration());
77         assertEquals(rootOverrideConfig, child2.getMergedOverrideConfiguration());
78         assertEquals(rootOverrideConfig, child2.getConfiguration());
79 
80         // Check child initial config if root has parent config set.
81         final Configuration rootParentConfig = new Configuration();
82         rootParentConfig.fontScale = 0.8f;
83         rootParentConfig.orientation = SCREEN_ORIENTATION_LANDSCAPE;
84         root.onConfigurationChanged(rootParentConfig);
85         final Configuration rootFullConfig = new Configuration(rootParentConfig);
86         rootFullConfig.updateFrom(rootOverrideConfig);
87 
88         final TestConfigurationContainer child3 = root.addChild();
89         assertEquals(EMPTY, child3.getRequestedOverrideConfiguration());
90         assertEquals(rootOverrideConfig, child3.getMergedOverrideConfiguration());
91         assertEquals(rootFullConfig, child3.getConfiguration());
92     }
93 
94     @Test
testConfigurationChangeOnAddRemove()95     public void testConfigurationChangeOnAddRemove() {
96         // Init root's config.
97         final TestConfigurationContainer root = new TestConfigurationContainer();
98         final Configuration rootOverrideConfig = new Configuration();
99         rootOverrideConfig.fontScale = 1.3f;
100         root.onRequestedOverrideConfigurationChanged(rootOverrideConfig);
101 
102         // Init child's config.
103         final TestConfigurationContainer child = root.addChild();
104         final Configuration childOverrideConfig = new Configuration();
105         childOverrideConfig.densityDpi = 320;
106         child.onRequestedOverrideConfigurationChanged(childOverrideConfig);
107         final Configuration mergedOverrideConfig = new Configuration(root.getConfiguration());
108         mergedOverrideConfig.updateFrom(childOverrideConfig);
109 
110         // Check configuration update when child is removed from parent.
111         root.removeChild(child);
112         assertEquals(childOverrideConfig, child.getRequestedOverrideConfiguration());
113         assertEquals(mergedOverrideConfig, child.getMergedOverrideConfiguration());
114         assertEquals(mergedOverrideConfig, child.getConfiguration());
115 
116         // It may be paranoia... but let's check if parent's config didn't change after removal.
117         assertEquals(rootOverrideConfig, root.getRequestedOverrideConfiguration());
118         assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration());
119         assertEquals(rootOverrideConfig, root.getConfiguration());
120 
121         // Init different root
122         final TestConfigurationContainer root2 = new TestConfigurationContainer();
123         final Configuration rootOverrideConfig2 = new Configuration();
124         rootOverrideConfig2.fontScale = 1.1f;
125         root2.onRequestedOverrideConfigurationChanged(rootOverrideConfig2);
126 
127         // Check configuration update when child is added to different parent.
128         mergedOverrideConfig.setTo(rootOverrideConfig2);
129         mergedOverrideConfig.updateFrom(childOverrideConfig);
130         root2.addChild(child);
131         assertEquals(childOverrideConfig, child.getRequestedOverrideConfiguration());
132         assertEquals(mergedOverrideConfig, child.getMergedOverrideConfiguration());
133         assertEquals(mergedOverrideConfig, child.getConfiguration());
134     }
135 
136     @Test
testConfigurationChangePropagation()137     public void testConfigurationChangePropagation() {
138         // Builds 3-level vertical hierarchy with one configuration container on each level.
139         // In addition to different overrides on each level, everyone in hierarchy will have one
140         // common overridden value - orientation;
141 
142         // Init root's config.
143         final TestConfigurationContainer root = new TestConfigurationContainer();
144         final Configuration rootOverrideConfig = new Configuration();
145         rootOverrideConfig.fontScale = 1.3f;
146         rootOverrideConfig.orientation = SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
147         root.onRequestedOverrideConfigurationChanged(rootOverrideConfig);
148 
149         // Init children.
150         final TestConfigurationContainer child1 = root.addChild();
151         final Configuration childOverrideConfig1 = new Configuration();
152         childOverrideConfig1.densityDpi = 320;
153         childOverrideConfig1.orientation = SCREEN_ORIENTATION_LANDSCAPE;
154         child1.onRequestedOverrideConfigurationChanged(childOverrideConfig1);
155 
156         final TestConfigurationContainer child2 = child1.addChild();
157         final Configuration childOverrideConfig2 = new Configuration();
158         childOverrideConfig2.screenWidthDp = 150;
159         childOverrideConfig2.orientation = SCREEN_ORIENTATION_PORTRAIT;
160         child2.onRequestedOverrideConfigurationChanged(childOverrideConfig2);
161 
162         // Check configuration on all levels when root override is updated.
163         rootOverrideConfig.smallestScreenWidthDp = 200;
164         root.onRequestedOverrideConfigurationChanged(rootOverrideConfig);
165 
166         final Configuration mergedOverrideConfig1 = new Configuration(rootOverrideConfig);
167         mergedOverrideConfig1.updateFrom(childOverrideConfig1);
168         final Configuration mergedConfig1 = new Configuration(mergedOverrideConfig1);
169 
170         final Configuration mergedOverrideConfig2 = new Configuration(mergedOverrideConfig1);
171         mergedOverrideConfig2.updateFrom(childOverrideConfig2);
172         final Configuration mergedConfig2 = new Configuration(mergedOverrideConfig2);
173 
174         assertEquals(rootOverrideConfig, root.getRequestedOverrideConfiguration());
175         assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration());
176         assertEquals(rootOverrideConfig, root.getConfiguration());
177 
178         assertEquals(childOverrideConfig1, child1.getRequestedOverrideConfiguration());
179         assertEquals(mergedOverrideConfig1, child1.getMergedOverrideConfiguration());
180         assertEquals(mergedConfig1, child1.getConfiguration());
181 
182         assertEquals(childOverrideConfig2, child2.getRequestedOverrideConfiguration());
183         assertEquals(mergedOverrideConfig2, child2.getMergedOverrideConfiguration());
184         assertEquals(mergedConfig2, child2.getConfiguration());
185 
186         // Check configuration on all levels when root parent config is updated.
187         final Configuration rootParentConfig = new Configuration();
188         rootParentConfig.screenHeightDp = 100;
189         rootParentConfig.orientation = SCREEN_ORIENTATION_REVERSE_PORTRAIT;
190         root.onConfigurationChanged(rootParentConfig);
191         final Configuration mergedRootConfig = new Configuration(rootParentConfig);
192         mergedRootConfig.updateFrom(rootOverrideConfig);
193 
194         mergedConfig1.setTo(mergedRootConfig);
195         mergedConfig1.updateFrom(mergedOverrideConfig1);
196 
197         mergedConfig2.setTo(mergedConfig1);
198         mergedConfig2.updateFrom(mergedOverrideConfig2);
199 
200         assertEquals(rootOverrideConfig, root.getRequestedOverrideConfiguration());
201         assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration());
202         assertEquals(mergedRootConfig, root.getConfiguration());
203 
204         assertEquals(childOverrideConfig1, child1.getRequestedOverrideConfiguration());
205         assertEquals(mergedOverrideConfig1, child1.getMergedOverrideConfiguration());
206         assertEquals(mergedConfig1, child1.getConfiguration());
207 
208         assertEquals(childOverrideConfig2, child2.getRequestedOverrideConfiguration());
209         assertEquals(mergedOverrideConfig2, child2.getMergedOverrideConfiguration());
210         assertEquals(mergedConfig2, child2.getConfiguration());
211     }
212 
213     @Test
testSetAlwaysOnTop()214     public void testSetAlwaysOnTop() {
215         final TestConfigurationContainer root = new TestConfigurationContainer();
216         final TestConfigurationContainer child1 = root.addChild();
217         final TestConfigurationContainer child2 = root.addChild();
218         root.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
219         root.setAlwaysOnTop(true);
220         final TestConfigurationContainer child3 = root.addChild();
221         assertEquals(true, root.isAlwaysOnTop());
222         assertEquals(false, child1.isAlwaysOnTop());
223         assertEquals(false, child2.isAlwaysOnTop());
224         assertEquals(false, child3.isAlwaysOnTop());
225     }
226 
227     @Test
testSetWindowingMode()228     public void testSetWindowingMode() {
229         final TestConfigurationContainer root = new TestConfigurationContainer();
230         root.setWindowingMode(WINDOWING_MODE_UNDEFINED);
231         final TestConfigurationContainer child = root.addChild();
232         child.setWindowingMode(WINDOWING_MODE_FREEFORM);
233         assertEquals(WINDOWING_MODE_UNDEFINED, root.getWindowingMode());
234         assertEquals(WINDOWING_MODE_FREEFORM, child.getWindowingMode());
235 
236         root.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
237         assertEquals(WINDOWING_MODE_FULLSCREEN, root.getWindowingMode());
238         assertEquals(WINDOWING_MODE_FREEFORM, child.getWindowingMode());
239     }
240 
241     @Test
testSetActivityType()242     public void testSetActivityType() {
243         final TestConfigurationContainer root = new TestConfigurationContainer();
244         root.setActivityType(ACTIVITY_TYPE_UNDEFINED);
245         final TestConfigurationContainer child = root.addChild();
246         child.setActivityType(ACTIVITY_TYPE_STANDARD);
247         assertEquals(ACTIVITY_TYPE_UNDEFINED, root.getActivityType());
248         assertEquals(ACTIVITY_TYPE_STANDARD, child.getActivityType());
249 
250         boolean gotException = false;
251         try {
252             // Can't change activity type once set.
253             child.setActivityType(ACTIVITY_TYPE_HOME);
254         } catch (IllegalStateException e) {
255             gotException = true;
256         }
257         assertTrue("Can't change activity type once set.", gotException);
258 
259         // TODO: Commenting out for now until we figure-out a good way to test these rules that
260         // should only apply to system process.
261         /*
262         gotException = false;
263         try {
264             // Parent can't change child's activity type once set.
265             root.setActivityType(ACTIVITY_TYPE_HOME);
266         } catch (IllegalStateException e) {
267             gotException = true;
268         }
269         assertTrue("Parent can't change activity type once set.", gotException);
270         assertEquals(ACTIVITY_TYPE_HOME, root.getActivityType());
271 
272         final TestConfigurationContainer child2 = new TestConfigurationContainer();
273         child2.setActivityType(ACTIVITY_TYPE_RECENTS);
274 
275         gotException = false;
276         try {
277             // Can't re-parent to a different activity type.
278             root.addChild(child2);
279         } catch (IllegalStateException e) {
280             gotException = true;
281         }
282         assertTrue("Can't re-parent to a different activity type.", gotException);
283         */
284 
285     }
286 
287     @Test
testRegisterConfigurationChangeListener()288     public void testRegisterConfigurationChangeListener() {
289         final TestConfigurationContainer container = new TestConfigurationContainer();
290         final TestConfigurationChangeListener listener = new TestConfigurationChangeListener();
291         final Configuration config = new Configuration();
292         config.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
293         config.windowConfiguration.setAppBounds(10, 10, 10, 10);
294         container.onRequestedOverrideConfigurationChanged(config);
295         container.registerConfigurationChangeListener(listener);
296         // Assert listener got the current config. of the container after it was registered.
297         assertEquals(config, listener.mOverrideConfiguration);
298         // Assert listener gets changes to override configuration.
299         container.onRequestedOverrideConfigurationChanged(EMPTY);
300         assertEquals(EMPTY, listener.mOverrideConfiguration);
301     }
302 
303     @Test
testConfigurationConstraints()304     public void testConfigurationConstraints() {
305         // Init root config.
306         final TestConfigurationContainer root = new TestConfigurationContainer();
307         final Configuration rootOverrideConfig = new Configuration();
308         rootOverrideConfig.smallestScreenWidthDp = 140;
309         root.onRequestedOverrideConfigurationChanged(rootOverrideConfig);
310 
311         // Init child with constraint
312         final TestConfigurationChangeListener listener = new TestConfigurationChangeListener();
313         final TestConfigurationContainer child1 = root.addConstraintChild();
314         child1.registerConfigurationChangeListener(listener);
315         final Configuration childOverrideConfig1 = new Configuration();
316         childOverrideConfig1.orientation = SCREEN_ORIENTATION_LANDSCAPE;
317         child1.onRequestedOverrideConfigurationChanged(childOverrideConfig1);
318 
319         assertEquals(SMALLEST_SCREEN_WIDTH_DP_UNDEFINED,
320                 child1.getRequestedOverrideConfiguration().smallestScreenWidthDp);
321         assertEquals(100, child1.getConfiguration().smallestScreenWidthDp);
322         assertEquals(100, listener.mOverrideConfiguration.smallestScreenWidthDp);
323 
324         // Check configuration on all levels when root override is updated.
325         rootOverrideConfig.smallestScreenWidthDp = 80;
326         root.onRequestedOverrideConfigurationChanged(rootOverrideConfig);
327 
328         assertEquals(SMALLEST_SCREEN_WIDTH_DP_UNDEFINED,
329                 child1.getRequestedOverrideConfiguration().smallestScreenWidthDp);
330         assertEquals(80, child1.getConfiguration().smallestScreenWidthDp);
331         assertEquals(80, listener.mOverrideConfiguration.smallestScreenWidthDp);
332 
333         rootOverrideConfig.smallestScreenWidthDp = 180;
334         root.onRequestedOverrideConfigurationChanged(rootOverrideConfig);
335 
336         assertEquals(SMALLEST_SCREEN_WIDTH_DP_UNDEFINED,
337                 child1.getRequestedOverrideConfiguration().smallestScreenWidthDp);
338         assertEquals(100, child1.getConfiguration().smallestScreenWidthDp);
339         assertEquals(100, child1.getMergedOverrideConfiguration().smallestScreenWidthDp);
340         assertEquals(100, listener.mOverrideConfiguration.smallestScreenWidthDp);
341     }
342 
343     @Test
testSetMaxBoundsByHierarchy()344     public void testSetMaxBoundsByHierarchy() {
345         final TestConfigurationContainer root =
346                 new TestConfigurationContainer(true /* providesMaxBounds */);
347         final Rect bounds = new Rect(0, 0, 10, 10);
348         final TestConfigurationContainer child = new TestConfigurationContainer();
349         root.addChild(child);
350 
351         root.setBounds(bounds);
352 
353         assertEquals(bounds, root.getBounds());
354         assertEquals(bounds, root.getConfiguration().windowConfiguration.getBounds());
355         assertEquals(bounds, child.getBounds());
356         assertEquals(bounds, child.getConfiguration().windowConfiguration.getBounds());
357 
358         assertEquals(bounds, root.getMaxBounds());
359         assertEquals(bounds, root.getConfiguration().windowConfiguration.getMaxBounds());
360         assertEquals(bounds, child.getMaxBounds());
361         assertEquals(bounds, child.getConfiguration().windowConfiguration.getMaxBounds());
362     }
363 
364     @Test
testSetBoundsNotOverrideMaxBounds()365     public void testSetBoundsNotOverrideMaxBounds() {
366         final TestConfigurationContainer root = new TestConfigurationContainer();
367         final Rect bounds = new Rect(0, 0, 10, 10);
368         final TestConfigurationContainer child = new TestConfigurationContainer();
369         root.addChild(child);
370 
371         root.setBounds(bounds);
372 
373         assertEquals(bounds, root.getBounds());
374         assertEquals(bounds, root.getConfiguration().windowConfiguration.getBounds());
375         assertEquals(bounds, child.getBounds());
376         assertEquals(bounds, child.getConfiguration().windowConfiguration.getBounds());
377 
378         assertTrue(root.getMaxBounds().isEmpty());
379         assertTrue(root.getConfiguration().windowConfiguration.getMaxBounds().isEmpty());
380         assertTrue(child.getMaxBounds().isEmpty());
381         assertTrue(child.getConfiguration().windowConfiguration.getMaxBounds().isEmpty());
382     }
383 
384     @Test
testOnRequestedOverrideConfigurationChangedOverrideMaxBounds()385     public void testOnRequestedOverrideConfigurationChangedOverrideMaxBounds() {
386         final TestConfigurationContainer root =
387                 new TestConfigurationContainer(true /* providesMaxBounds */);
388         final Rect bounds = new Rect(0, 0, 10, 10);
389         final TestConfigurationContainer child = new TestConfigurationContainer();
390         root.addChild(child);
391         final Configuration configuration = new Configuration();
392         configuration.windowConfiguration.setBounds(bounds);
393 
394         root.onRequestedOverrideConfigurationChanged(configuration);
395 
396         assertEquals(bounds, root.getBounds());
397         assertEquals(bounds, root.getConfiguration().windowConfiguration.getBounds());
398         assertEquals(bounds, child.getBounds());
399         assertEquals(bounds, child.getConfiguration().windowConfiguration.getBounds());
400 
401         assertEquals(bounds, root.getMaxBounds());
402         assertEquals(bounds, root.getConfiguration().windowConfiguration.getMaxBounds());
403         assertEquals(bounds, child.getMaxBounds());
404         assertEquals(bounds, child.getConfiguration().windowConfiguration.getMaxBounds());
405     }
406 
407 
408     /**
409      * Contains minimal implementation of {@link ConfigurationContainer}'s abstract behavior needed
410      * for testing.
411      */
412     private class TestConfigurationContainer
413             extends ConfigurationContainer<TestConfigurationContainer> {
414         private List<TestConfigurationContainer> mChildren = new ArrayList<>();
415         private TestConfigurationContainer mParent;
416 
417         private boolean mProvidesMaxBounds = false;
418 
TestConfigurationContainer()419         TestConfigurationContainer() {}
420 
TestConfigurationContainer(boolean providesMaxBounds)421         TestConfigurationContainer(boolean providesMaxBounds) {
422             mProvidesMaxBounds = providesMaxBounds;
423         }
424 
addChild(TestConfigurationContainer childContainer)425         TestConfigurationContainer addChild(TestConfigurationContainer childContainer) {
426             final ConfigurationContainer oldParent = childContainer.getParent();
427             childContainer.mParent = this;
428             childContainer.onParentChanged(this, oldParent);
429             mChildren.add(childContainer);
430             return childContainer;
431         }
432 
addChild()433         TestConfigurationContainer addChild() {
434             return addChild(new TestConfigurationContainer());
435         }
436 
addConstraintChild()437         TestConfigurationContainer addConstraintChild() {
438             return addChild(new TestConfigurationContainerWithConstraints());
439         }
440 
removeChild(TestConfigurationContainer child)441         void removeChild(TestConfigurationContainer child) {
442             final ConfigurationContainer oldParent = child.getParent();
443             child.mParent = null;
444             child.onParentChanged(null, oldParent);
445         }
446 
447         @Override
getChildCount()448         protected int getChildCount() {
449             return mChildren.size();
450         }
451 
452         @Override
getChildAt(int index)453         protected TestConfigurationContainer getChildAt(int index) {
454             return mChildren.get(index);
455         }
456 
457         @Override
getParent()458         protected ConfigurationContainer getParent() {
459             return mParent;
460         }
461 
462         @Override
providesMaxBounds()463         public boolean providesMaxBounds() {
464             return mProvidesMaxBounds;
465         }
466     }
467 
468     /**
469      * Contains minimal implementation of {@link ConfigurationContainer}'s abstract behavior needed
470      * for testing.
471      */
472     private class TestConfigurationContainerWithConstraints
473             extends TestConfigurationContainer {
474 
475         @Override
resolveOverrideConfiguration(Configuration newParentConfig)476         public void resolveOverrideConfiguration(Configuration newParentConfig) {
477             // Restrict smallestScreenWidthDp to 100
478             getResolvedOverrideConfiguration().setTo(getRequestedOverrideConfiguration());
479             int smallestScreenWidthDp =
480                     getResolvedOverrideConfiguration().smallestScreenWidthDp
481                             == SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
482                     ? newParentConfig.smallestScreenWidthDp
483                         : getResolvedOverrideConfiguration().smallestScreenWidthDp;
484             if (smallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
485                 getResolvedOverrideConfiguration().smallestScreenWidthDp =
486                         Math.min(smallestScreenWidthDp, 100);
487             }
488         }
489     }
490 
491     private static class TestConfigurationChangeListener implements ConfigurationContainerListener {
492 
493         final Configuration mOverrideConfiguration = new Configuration();
494 
495         @Override
onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration)496         public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
497             mOverrideConfiguration.setTo(overrideConfiguration);
498         }
499     }
500 }
501