1 /*
2  * Copyright (C) 2016 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.server.wm;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
20 import static android.server.wm.ComponentNameUtils.getWindowName;
21 import static android.server.wm.WindowManagerState.dpToPx;
22 import static android.server.wm.app.Components.BOTTOM_LEFT_LAYOUT_ACTIVITY;
23 import static android.server.wm.app.Components.BOTTOM_RIGHT_LAYOUT_ACTIVITY;
24 import static android.server.wm.app.Components.TEST_ACTIVITY;
25 import static android.server.wm.app.Components.TOP_LEFT_LAYOUT_ACTIVITY;
26 import static android.server.wm.app.Components.TOP_RIGHT_LAYOUT_ACTIVITY;
27 import static android.view.Display.DEFAULT_DISPLAY;
28 import static android.view.WindowInsets.Type.captionBar;
29 import static android.view.WindowInsets.Type.systemBars;
30 
31 import static org.junit.Assert.assertEquals;
32 import static org.junit.Assert.assertNotNull;
33 import static org.junit.Assume.assumeTrue;
34 
35 import android.content.ComponentName;
36 import android.graphics.Rect;
37 import android.platform.test.annotations.Presubmit;
38 import android.server.wm.WindowManagerState.WindowState;
39 import android.view.DisplayCutout;
40 import android.view.WindowMetrics;
41 
42 import org.junit.Test;
43 
44 import java.util.List;
45 
46 /**
47  * Build/Install/Run:
48  *     atest CtsWindowManagerDeviceTestCases:ManifestLayoutTests
49  */
50 @Presubmit
51 public class ManifestLayoutTests extends ActivityManagerTestBase {
52 
53     // Test parameters
54     private static final int DEFAULT_WIDTH_DP = 240;
55     private static final int DEFAULT_HEIGHT_DP = 160;
56     private static final float DEFAULT_WIDTH_FRACTION = 0.50f;
57     private static final float DEFAULT_HEIGHT_FRACTION = 0.70f;
58     private static final int MIN_WIDTH_DP = 100;
59     private static final int MIN_HEIGHT_DP = 80;
60 
61     private static final int GRAVITY_VER_CENTER = 0x01;
62     private static final int GRAVITY_VER_TOP    = 0x02;
63     private static final int GRAVITY_VER_BOTTOM = 0x04;
64     private static final int GRAVITY_HOR_CENTER = 0x10;
65     private static final int GRAVITY_HOR_LEFT   = 0x20;
66     private static final int GRAVITY_HOR_RIGHT  = 0x40;
67 
68     private WindowManagerState.DisplayContent mDisplay;
69     private WindowState mWindowState;
70 
71     @Test
testGravityAndDefaultSizeTopLeft()72     public void testGravityAndDefaultSizeTopLeft() throws Exception {
73         testLayout(GRAVITY_VER_TOP, GRAVITY_HOR_LEFT, false /*fraction*/);
74     }
75 
76     @Test
testGravityAndDefaultSizeTopRight()77     public void testGravityAndDefaultSizeTopRight() throws Exception {
78         testLayout(GRAVITY_VER_TOP, GRAVITY_HOR_RIGHT, true /*fraction*/);
79     }
80 
81     @Test
testGravityAndDefaultSizeBottomLeft()82     public void testGravityAndDefaultSizeBottomLeft() throws Exception {
83         testLayout(GRAVITY_VER_BOTTOM, GRAVITY_HOR_LEFT, true /*fraction*/);
84     }
85 
86     @Test
testGravityAndDefaultSizeBottomRight()87     public void testGravityAndDefaultSizeBottomRight() throws Exception {
88         testLayout(GRAVITY_VER_BOTTOM, GRAVITY_HOR_RIGHT, false /*fraction*/);
89     }
90 
91     @Test
testMinimalSizeFreeform()92     public void testMinimalSizeFreeform() throws Exception {
93         assumeTrue("Skipping test: no freeform support", supportsFreeform());
94 
95         testMinimalSize(true /* freeform */);
96     }
97 
98     @Test
99     @Presubmit
testMinimalSizeDocked()100     public void testMinimalSizeDocked() throws Exception {
101         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
102 
103         testMinimalSize(false /* freeform */);
104     }
105 
testMinimalSize(boolean freeform)106     private void testMinimalSize(boolean freeform) throws Exception {
107         // Issue command to resize to <0,0,1,1>. We expect the size to be floored at
108         // MIN_WIDTH_DPxMIN_HEIGHT_DP.
109         if (freeform) {
110             launchActivity(BOTTOM_RIGHT_LAYOUT_ACTIVITY, WINDOWING_MODE_FREEFORM);
111             resizeActivityTask(BOTTOM_RIGHT_LAYOUT_ACTIVITY, 0, 0, 1, 1);
112         } else { // stackId == DOCKED_STACK_ID
113             launchActivitiesInSplitScreen(
114                     getLaunchActivityBuilder().setTargetActivity(BOTTOM_RIGHT_LAYOUT_ACTIVITY),
115                     getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
116             mTaskOrganizer.setRootPrimaryTaskBounds(new Rect(0, 0, 1, 1));
117         }
118         getDisplayAndWindowState(BOTTOM_RIGHT_LAYOUT_ACTIVITY, false);
119 
120         final int minWidth = dpToPx(MIN_WIDTH_DP, mDisplay.getDpi());
121         final int minHeight = dpToPx(MIN_HEIGHT_DP, mDisplay.getDpi());
122         final Rect containingRect = mWindowState.getContainingFrame();
123         final int cutoutSize = getCutoutSizeByHorGravity(GRAVITY_HOR_LEFT);
124 
125         assertEquals("Min width is incorrect", minWidth,
126                 containingRect.width() + cutoutSize);
127         assertEquals("Min height is incorrect", minHeight, containingRect.height());
128     }
129 
testLayout( int vGravity, int hGravity, boolean fraction)130     private void testLayout(
131             int vGravity, int hGravity, boolean fraction) throws Exception {
132         assumeTrue("Skipping test: no freeform support", supportsFreeform());
133 
134         final ComponentName activityName;
135         if (vGravity == GRAVITY_VER_TOP) {
136             activityName = (hGravity == GRAVITY_HOR_LEFT) ? TOP_LEFT_LAYOUT_ACTIVITY
137                     : TOP_RIGHT_LAYOUT_ACTIVITY;
138         } else {
139             activityName = (hGravity == GRAVITY_HOR_LEFT) ? BOTTOM_LEFT_LAYOUT_ACTIVITY
140                     : BOTTOM_RIGHT_LAYOUT_ACTIVITY;
141         }
142 
143         // Launch in freeform stack
144         launchActivity(activityName, WINDOWING_MODE_FREEFORM);
145 
146         getDisplayAndWindowState(activityName, true);
147 
148         final Rect containingRect = mWindowState.getContainingFrame();
149         final WindowMetrics windowMetrics = mWm.getMaximumWindowMetrics();
150         final Rect stableBounds = new Rect(windowMetrics.getBounds());
151         stableBounds.inset(windowMetrics.getWindowInsets().getInsetsIgnoringVisibility(
152                 systemBars() & ~captionBar()));
153         final int expectedWidthPx, expectedHeightPx;
154         // Evaluate the expected window size in px. If we're using fraction dimensions,
155         // calculate the size based on the app rect size. Otherwise, convert the expected
156         // size in dp to px.
157         if (fraction) {
158             expectedWidthPx = (int) (stableBounds.width() * DEFAULT_WIDTH_FRACTION);
159             expectedHeightPx = (int) (stableBounds.height() * DEFAULT_HEIGHT_FRACTION);
160         } else {
161             final int densityDpi = mDisplay.getDpi();
162             expectedWidthPx = dpToPx(DEFAULT_WIDTH_DP, densityDpi);
163             expectedHeightPx = dpToPx(DEFAULT_HEIGHT_DP, densityDpi);
164         }
165 
166         verifyFrameSizeAndPosition(vGravity, hGravity, expectedWidthPx, expectedHeightPx,
167                 containingRect, stableBounds);
168     }
169 
getDisplayAndWindowState(ComponentName activityName, boolean checkFocus)170     private void getDisplayAndWindowState(ComponentName activityName, boolean checkFocus)
171             throws Exception {
172         final String windowName = getWindowName(activityName);
173 
174         mWmState.computeState(activityName);
175 
176         if (checkFocus) {
177             mWmState.assertFocusedWindow("Test window must be the front window.", windowName);
178         } else {
179             mWmState.assertVisibility(activityName, true);
180         }
181 
182         final List<WindowState> windowList =
183                 mWmState.getMatchingVisibleWindowState(windowName);
184 
185         assertEquals("Should have exactly one window state for the activity.",
186                 1, windowList.size());
187 
188         mWindowState = windowList.get(0);
189         assertNotNull("Should have a valid window", mWindowState);
190 
191         mDisplay = mWmState.getDisplay(mWindowState.getDisplayId());
192         assertNotNull("Should be on a display", mDisplay);
193     }
194 
verifyFrameSizeAndPosition( int vGravity, int hGravity, int expectedWidthPx, int expectedHeightPx, Rect containingFrame, Rect parentFrame)195     private void verifyFrameSizeAndPosition(
196             int vGravity, int hGravity, int expectedWidthPx, int expectedHeightPx,
197             Rect containingFrame, Rect parentFrame) {
198         final int cutoutSize = getCutoutSizeByHorGravity(hGravity);
199         assertEquals("Width is incorrect",
200                 expectedWidthPx, containingFrame.width() + cutoutSize);
201         assertEquals("Height is incorrect", expectedHeightPx, containingFrame.height());
202 
203         if (vGravity == GRAVITY_VER_TOP) {
204             assertEquals("Should be on the top", parentFrame.top, containingFrame.top);
205         } else if (vGravity == GRAVITY_VER_BOTTOM) {
206             assertEquals("Should be on the bottom", parentFrame.bottom, containingFrame.bottom);
207         }
208 
209         if (hGravity == GRAVITY_HOR_LEFT) {
210             assertEquals("Should be on the left",
211                     parentFrame.left, containingFrame.left - cutoutSize);
212         } else if (hGravity == GRAVITY_HOR_RIGHT){
213             assertEquals("Should be on the right",
214                     parentFrame.right, containingFrame.right + cutoutSize);
215         }
216     }
217 
getCutoutSizeByHorGravity(int hGravity)218     private int getCutoutSizeByHorGravity(int hGravity) {
219         DisplayCutout cutout = mDm.getDisplay(DEFAULT_DISPLAY).getCutout();
220         if (cutout == null) {
221             return 0;
222         }
223 
224         // When the layoutInDisplayCutoutMode is default, the status bar & navigation bar already
225         // take top and bottom cutout into account.
226         // Here we only need to account for left & right cutout areas.
227         if (hGravity == GRAVITY_HOR_LEFT) {
228             return cutout.getSafeInsetLeft();
229         } else if (hGravity == GRAVITY_HOR_RIGHT) {
230             return cutout.getSafeInsetRight();
231         } else {
232             return 0;
233         }
234     }
235 }
236