1 /*
2  * Copyright (C) 2011 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 android.dpi.cts;
18 
19 import static android.content.res.Configuration.SCREENLAYOUT_LONG_MASK;
20 import static android.content.res.Configuration.SCREENLAYOUT_LONG_NO;
21 import static android.content.res.Configuration.SCREENLAYOUT_LONG_YES;
22 import static android.content.res.Configuration.SCREENLAYOUT_SIZE_LARGE;
23 import static android.content.res.Configuration.SCREENLAYOUT_SIZE_MASK;
24 import static android.content.res.Configuration.SCREENLAYOUT_SIZE_NORMAL;
25 import static android.content.res.Configuration.SCREENLAYOUT_SIZE_XLARGE;
26 import static android.view.WindowInsets.Type.displayCutout;
27 import static android.view.WindowInsets.Type.systemBars;
28 
29 import android.app.Activity;
30 import android.content.Intent;
31 import android.content.pm.ActivityInfo;
32 import android.content.pm.PackageManager;
33 import android.content.res.Configuration;
34 import android.graphics.Insets;
35 import android.graphics.Rect;
36 import android.server.wm.IgnoreOrientationRequestSession;
37 import android.server.wm.WindowManagerStateHelper;
38 import android.test.ActivityInstrumentationTestCase2;
39 import android.view.WindowInsets;
40 import android.view.WindowMetrics;
41 
42 import com.android.window.flags.Flags;
43 
44 public class ConfigurationScreenLayoutTest
45         extends ActivityInstrumentationTestCase2<OrientationActivity> {
46 
47     private static final int[] ORIENTATIONS = new int[] {
48             ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
49             ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
50             ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
51             ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE,
52     };
53 
54     private static final int BIGGEST_LAYOUT = SCREENLAYOUT_SIZE_XLARGE
55             | SCREENLAYOUT_LONG_YES;
56 
ConfigurationScreenLayoutTest()57     public ConfigurationScreenLayoutTest() {
58         super(OrientationActivity.class);
59     }
60 
testScreenLayout()61     public void testScreenLayout() throws Exception {
62         if (!supportsRotation()) {
63             // test has no effect if device does not support rotation
64             tearDown();
65             return;
66         }
67         if (isPC()) {
68             // The test skips mainly for Chromebook clamshell mode. For Chromebook clamshell mode
69             // with non-rotated landscape physical screen, the portrait window/activity has special
70             // behavior with black background on both sides to make the window/activity look
71             // portrait, which returns smaller screen layout size.
72             tearDown();
73             return;
74         }
75         // Disable IgnoreOrientationRequest feature because when it's enabled, the device would only
76         // follow physical rotations.
77         try (IgnoreOrientationRequestSession session =
78                      new IgnoreOrientationRequestSession(false /* enable */)) {
79 
80             // Check that all four orientations report the same configuration value.
81             for (int orientation : ORIENTATIONS) {
82                 Activity activity = startOrientationActivity(orientation);
83                 WindowManagerStateHelper wmState = new WindowManagerStateHelper();
84                 wmState.computeState();
85                 if (activity.isInMultiWindowMode()
86                         || wmState.isTaskDisplayAreaIgnoringOrientationRequest(
87                                 activity.getComponentName())) {
88                     // activity.setRequestedOrientation has no effect in multi-window mode.
89                     // Besides, the test is not feasible when current display area is ignoring
90                     // orientation since configuration will be updated, so it goes to quit this
91                     // test as well.
92                     tearDown();
93                     return;
94                 }
95                 final int expectedLayout = computeScreenLayout(activity);
96                 final int expectedSize = expectedLayout & SCREENLAYOUT_SIZE_MASK;
97                 final int expectedLong = expectedLayout & SCREENLAYOUT_LONG_MASK;
98 
99                 final Configuration config = activity.getResources().getConfiguration();
100                 final int actualSize = config.screenLayout & SCREENLAYOUT_SIZE_MASK;
101                 final int actualLong = config.screenLayout & SCREENLAYOUT_LONG_MASK;
102 
103                 assertEquals("Expected screen size value of " + expectedSize + " but got "
104                         + actualSize + " for orientation "
105                         + orientation, expectedSize, actualSize);
106                 assertEquals("Expected screen long value of " + expectedLong + " but got "
107                         + actualLong + " for orientation "
108                         + orientation, expectedLong, actualLong);
109                 tearDown();
110             }
111         } finally {
112             tearDown();
113         }
114     }
115 
hasDeviceFeature(final String requiredFeature)116     private boolean hasDeviceFeature(final String requiredFeature) {
117         return getInstrumentation().getContext()
118                 .getPackageManager()
119                 .hasSystemFeature(requiredFeature);
120     }
121 
startOrientationActivity(int orientation)122     private Activity startOrientationActivity(int orientation) {
123         Intent intent = new Intent();
124         intent.putExtra(OrientationActivity.EXTRA_ORIENTATION, orientation);
125         setActivityIntent(intent);
126         return getActivity();
127     }
128 
129     // Logic copied from Configuration#reduceScreenLayout(int, int, int)
130     /**
131      * Returns expected value of {@link Configuration#screenLayout} with the
132      *         {@link Configuration#SCREENLAYOUT_LONG_MASK} and
133      *         {@link Configuration#SCREENLAYOUT_SIZE_MASK} defined
134      */
computeScreenLayout(Activity activity)135     private int computeScreenLayout(Activity activity) {
136         final Insets insets;
137         if (!Flags.insetsDecoupledConfiguration()) {
138             final WindowInsets windowInsets = activity.getWindowManager().getCurrentWindowMetrics()
139                     .getWindowInsets();
140             insets = windowInsets.getInsets(systemBars() | displayCutout());
141         } else {
142             insets = Insets.NONE;
143         }
144         return reduceScreenLayout(activity, insets, BIGGEST_LAYOUT);
145     }
146 
reduceScreenLayout(Activity activity, Insets excludeInsets, int screenLayout)147     private int reduceScreenLayout(Activity activity, Insets excludeInsets, int screenLayout) {
148         int screenLayoutSize;
149         boolean screenLayoutLong;
150 
151         final WindowMetrics windowMetrics = activity.getWindowManager().getCurrentWindowMetrics();
152         final Rect bounds = new Rect(windowMetrics.getBounds());
153         bounds.inset(excludeInsets);
154 
155         final float density = activity.getResources().getDisplayMetrics().density;
156         final int widthDp = (int) (bounds.width() / density);
157         final int heightDp = (int) (bounds.height() / density);
158         final int longSize = Math.max(widthDp, heightDp);
159         final int shortSize = Math.min(widthDp, heightDp);
160 
161         if (longSize < 470) {
162             screenLayoutSize = Configuration.SCREENLAYOUT_SIZE_SMALL;
163             screenLayoutLong = false;
164         } else {
165             if (longSize >= 960 && shortSize >= 720) {
166                 screenLayoutSize = SCREENLAYOUT_SIZE_XLARGE;
167             } else if (longSize >= 640 && shortSize >= 480) {
168                 screenLayoutSize = SCREENLAYOUT_SIZE_LARGE;
169             } else {
170                 screenLayoutSize = SCREENLAYOUT_SIZE_NORMAL;
171             }
172             screenLayoutLong = ((longSize * 3) / 5) >= (shortSize - 1);
173         }
174 
175         if (!screenLayoutLong) {
176             screenLayout = (screenLayout & ~SCREENLAYOUT_LONG_MASK) | SCREENLAYOUT_LONG_NO;
177         }
178         int curSize = screenLayout & SCREENLAYOUT_SIZE_MASK;
179         if (screenLayoutSize < curSize) {
180             screenLayout = (screenLayout & ~SCREENLAYOUT_SIZE_MASK) | screenLayoutSize;
181         }
182         return screenLayout;
183     }
184 
185     /**
186      * Rotation support is indicated by explicitly having both landscape and portrait
187      * features or not listing either at all.
188      */
supportsRotation()189     private boolean supportsRotation() {
190         final boolean supportsLandscape = hasDeviceFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE);
191         final boolean supportsPortrait = hasDeviceFeature(PackageManager.FEATURE_SCREEN_PORTRAIT);
192         return (supportsLandscape && supportsPortrait)
193                 || (!supportsLandscape && !supportsPortrait);
194     }
195 
196     /** Checks if it is a PC device */
isPC()197     private boolean isPC() {
198         return hasDeviceFeature(PackageManager.FEATURE_PC);
199     }
200 }
201