1 /*
2  * Copyright (C) 2023 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.view;
18 
19 import static android.view.InsetsSource.ID_IME;
20 import static android.view.WindowInsets.Type.displayCutout;
21 import static android.view.WindowInsets.Type.ime;
22 import static android.view.WindowInsets.Type.navigationBars;
23 import static android.view.WindowInsets.Type.statusBars;
24 import static android.view.WindowLayout.UNSPECIFIED_LENGTH;
25 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
26 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
27 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
28 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
29 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
30 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
31 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
32 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
33 
34 import static org.junit.Assert.assertEquals;
35 
36 import android.app.WindowConfiguration;
37 import android.graphics.Insets;
38 import android.graphics.Rect;
39 import android.platform.test.annotations.Presubmit;
40 import android.window.ClientWindowFrames;
41 
42 import androidx.test.filters.SmallTest;
43 
44 import org.junit.Before;
45 import org.junit.Test;
46 
47 /**
48  * Tests for the {@link WindowLayout#computeFrames}.
49  *
50  * Build/Install/Run:
51  *  atest WmTests:WindowLayoutTests
52  */
53 @SmallTest
54 @Presubmit
55 public class WindowLayoutTests {
56     private static final int DISPLAY_WIDTH = 500;
57     private static final int DISPLAY_HEIGHT = 1000;
58     private static final int STATUS_BAR_HEIGHT = 10;
59     private static final int NAVIGATION_BAR_HEIGHT = 15;
60     private static final int IME_HEIGHT = 400;
61     private static final int DISPLAY_CUTOUT_HEIGHT = 8;
62     private static final Rect DISPLAY_CUTOUT_BOUNDS_TOP =
63             new Rect(DISPLAY_WIDTH / 4, 0, DISPLAY_WIDTH * 3 / 4, DISPLAY_CUTOUT_HEIGHT);
64     private static final Insets WATERFALL_INSETS = Insets.of(6, 0, 12, 0);
65 
66     private static final int ID_STATUS_BAR = InsetsSource.createId(
67             null /* owner */, 0 /* index */, statusBars());
68     private static final int ID_NAVIGATION_BAR = InsetsSource.createId(
69             null /* owner */, 0 /* index */, navigationBars());
70 
71     private final WindowLayout mWindowLayout = new WindowLayout();
72     private final ClientWindowFrames mFrames = new ClientWindowFrames();
73 
74     private WindowManager.LayoutParams mAttrs;
75     private InsetsState mState;
76     private final Rect mDisplayCutoutSafe = new Rect();
77     private Rect mWindowBounds;
78     private int mWindowingMode;
79     private int mRequestedWidth;
80     private int mRequestedHeight;
81     private int mRequestedVisibleTypes;
82     private float mCompatScale;
83 
84     @Before
setUp()85     public void setUp() {
86         mAttrs = new WindowManager.LayoutParams();
87         mState = new InsetsState();
88         mState.setDisplayFrame(new Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT));
89         mState.getOrCreateSource(ID_STATUS_BAR, statusBars()).setFrame(
90                 0, 0, DISPLAY_WIDTH, STATUS_BAR_HEIGHT);
91         mState.getOrCreateSource(ID_NAVIGATION_BAR, navigationBars()).setFrame(
92                 0, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT);
93         mState.getDisplayCutoutSafe(mDisplayCutoutSafe);
94         mWindowBounds = new Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
95         mWindowingMode = WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
96         mRequestedWidth = DISPLAY_WIDTH;
97         mRequestedHeight = DISPLAY_HEIGHT;
98         mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
99         mCompatScale = 1f;
100         mFrames.attachedFrame = null;
101     }
102 
computeFrames()103     private void computeFrames() {
104         mWindowLayout.computeFrames(mAttrs, mState, mDisplayCutoutSafe, mWindowBounds,
105                 mWindowingMode, mRequestedWidth, mRequestedHeight, mRequestedVisibleTypes,
106                 mCompatScale, mFrames);
107     }
108 
addDisplayCutout()109     private void addDisplayCutout() {
110         mState.setDisplayCutout(new DisplayCutout(
111                 Insets.of(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0),
112                 new Rect(),
113                 DISPLAY_CUTOUT_BOUNDS_TOP,
114                 new Rect(),
115                 new Rect(),
116                 WATERFALL_INSETS));
117         mState.getDisplayCutoutSafe(mDisplayCutoutSafe);
118         mState.getOrCreateSource(InsetsSource.createId(null, 0, displayCutout()), displayCutout())
119                 .setFrame(0, 0, mDisplayCutoutSafe.left, DISPLAY_HEIGHT);
120         mState.getOrCreateSource(InsetsSource.createId(null, 1, displayCutout()), displayCutout())
121                 .setFrame(0, 0, DISPLAY_WIDTH, mDisplayCutoutSafe.top);
122         mState.getOrCreateSource(InsetsSource.createId(null, 2, displayCutout()), displayCutout())
123                 .setFrame(mDisplayCutoutSafe.right, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
124         mState.getOrCreateSource(InsetsSource.createId(null, 3, displayCutout()), displayCutout())
125                 .setFrame(0, mDisplayCutoutSafe.bottom, DISPLAY_WIDTH, DISPLAY_HEIGHT);
126     }
127 
assertInsetByTopBottom(int top, int bottom, Rect actual)128     private static void assertInsetByTopBottom(int top, int bottom, Rect actual) {
129         assertInsetBy(0, top, 0, bottom, actual);
130     }
131 
assertInsetBy(int left, int top, int right, int bottom, Rect actual)132     private static void assertInsetBy(int left, int top, int right, int bottom, Rect actual) {
133         assertRect(left, top, DISPLAY_WIDTH - right, DISPLAY_HEIGHT - bottom, actual);
134     }
135 
assertRect(int left, int top, int right, int bottom, Rect actual)136     private static void assertRect(int left, int top, int right, int bottom, Rect actual) {
137         assertEquals(new Rect(left, top, right, bottom), actual);
138     }
139 
140     @Test
defaultParams()141     public void defaultParams() {
142         computeFrames();
143 
144         assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
145         assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame);
146         assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.frame);
147     }
148 
149     @Test
unmeasured()150     public void unmeasured() {
151         mRequestedWidth = UNSPECIFIED_LENGTH;
152         mRequestedHeight = UNSPECIFIED_LENGTH;
153         computeFrames();
154 
155         assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
156         assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame);
157         assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.frame);
158     }
159 
160     @Test
unmeasuredWithSizeSpecifiedInLayoutParams()161     public void unmeasuredWithSizeSpecifiedInLayoutParams() {
162         final int width = DISPLAY_WIDTH / 2;
163         final int height = DISPLAY_HEIGHT / 2;
164         mRequestedWidth = UNSPECIFIED_LENGTH;
165         mRequestedHeight = UNSPECIFIED_LENGTH;
166         mAttrs.width = width;
167         mAttrs.height = height;
168         mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
169         computeFrames();
170 
171         assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
172         assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame);
173         assertRect(0, STATUS_BAR_HEIGHT, width, STATUS_BAR_HEIGHT + height, mFrames.frame);
174     }
175 
176     @Test
nonFullscreenWindowBounds()177     public void nonFullscreenWindowBounds() {
178         final int top = Math.max(DISPLAY_HEIGHT / 2, STATUS_BAR_HEIGHT);
179         mWindowBounds.set(0, top, DISPLAY_WIDTH, DISPLAY_HEIGHT);
180         mRequestedWidth = UNSPECIFIED_LENGTH;
181         mRequestedHeight = UNSPECIFIED_LENGTH;
182         computeFrames();
183 
184         assertRect(0, top, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT,
185                 mFrames.displayFrame);
186         assertRect(0, top, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT,
187                 mFrames.parentFrame);
188         assertRect(0, top, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT,
189                 mFrames.frame);
190     }
191 
192     @Test
attachedFrame()193     public void attachedFrame() {
194         final int bottom = (DISPLAY_HEIGHT - STATUS_BAR_HEIGHT - NAVIGATION_BAR_HEIGHT) / 2;
195         mFrames.attachedFrame = new Rect(0, STATUS_BAR_HEIGHT, DISPLAY_WIDTH, bottom);
196         mRequestedWidth = UNSPECIFIED_LENGTH;
197         mRequestedHeight = UNSPECIFIED_LENGTH;
198         computeFrames();
199 
200         assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
201         assertEquals(mFrames.attachedFrame, mFrames.parentFrame);
202         assertEquals(mFrames.attachedFrame, mFrames.frame);
203     }
204 
205     @Test
fitStatusBars()206     public void fitStatusBars() {
207         mAttrs.setFitInsetsTypes(WindowInsets.Type.statusBars());
208         computeFrames();
209 
210         assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.displayFrame);
211         assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.parentFrame);
212         assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.frame);
213     }
214 
215     @Test
fitNavigationBars()216     public void fitNavigationBars() {
217         mAttrs.setFitInsetsTypes(WindowInsets.Type.navigationBars());
218         computeFrames();
219 
220         assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
221         assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame);
222         assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mFrames.frame);
223     }
224 
225     @Test
fitZeroTypes()226     public void fitZeroTypes() {
227         mAttrs.setFitInsetsTypes(0);
228         computeFrames();
229 
230         assertInsetByTopBottom(0, 0, mFrames.displayFrame);
231         assertInsetByTopBottom(0, 0, mFrames.parentFrame);
232         assertInsetByTopBottom(0, 0, mFrames.frame);
233     }
234 
235     @Test
fitAllSides()236     public void fitAllSides() {
237         mAttrs.setFitInsetsSides(WindowInsets.Side.all());
238         computeFrames();
239 
240         assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
241         assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame);
242         assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.frame);
243     }
244 
245     @Test
fitTopOnly()246     public void fitTopOnly() {
247         mAttrs.setFitInsetsSides(WindowInsets.Side.TOP);
248         computeFrames();
249 
250         assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.displayFrame);
251         assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.parentFrame);
252         assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.frame);
253     }
254 
255     @Test
fitZeroSides()256     public void fitZeroSides() {
257         mAttrs.setFitInsetsSides(0);
258         computeFrames();
259 
260         assertInsetByTopBottom(0, 0, mFrames.displayFrame);
261         assertInsetByTopBottom(0, 0, mFrames.parentFrame);
262         assertInsetByTopBottom(0, 0, mFrames.frame);
263     }
264 
265     @Test
fitInvisibleInsets()266     public void fitInvisibleInsets() {
267         mState.setSourceVisible(ID_STATUS_BAR, false);
268         mState.setSourceVisible(ID_NAVIGATION_BAR, false);
269         computeFrames();
270 
271         assertInsetByTopBottom(0, 0, mFrames.displayFrame);
272         assertInsetByTopBottom(0, 0, mFrames.parentFrame);
273         assertInsetByTopBottom(0, 0, mFrames.frame);
274     }
275 
276     @Test
fitInvisibleInsetsIgnoringVisibility()277     public void fitInvisibleInsetsIgnoringVisibility() {
278         mState.setSourceVisible(ID_STATUS_BAR, false);
279         mState.setSourceVisible(ID_NAVIGATION_BAR, false);
280         mAttrs.setFitInsetsIgnoringVisibility(true);
281         computeFrames();
282 
283         assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
284         assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame);
285         assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.frame);
286     }
287 
288     @Test
insetParentFrameByIme()289     public void insetParentFrameByIme() {
290         mState.getOrCreateSource(ID_IME, ime())
291                 .setVisible(true)
292                 .setFrame(0, DISPLAY_HEIGHT - IME_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT);
293         mAttrs.privateFlags |= PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
294         computeFrames();
295 
296         assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
297         assertInsetByTopBottom(STATUS_BAR_HEIGHT, IME_HEIGHT, mFrames.parentFrame);
298         assertInsetByTopBottom(STATUS_BAR_HEIGHT, IME_HEIGHT, mFrames.frame);
299     }
300 
301     @Test
fitDisplayCutout()302     public void fitDisplayCutout() {
303         addDisplayCutout();
304         mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
305         mAttrs.setFitInsetsTypes(WindowInsets.Type.displayCutout());
306         computeFrames();
307 
308         assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
309                 mFrames.displayFrame);
310         assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
311                 mFrames.parentFrame);
312         assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
313                 mFrames.frame);
314     }
315 
316     @Test
layoutInDisplayCutoutModeDefault()317     public void layoutInDisplayCutoutModeDefault() {
318         addDisplayCutout();
319         mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
320         mAttrs.setFitInsetsTypes(0);
321         computeFrames();
322 
323         assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
324                 mFrames.displayFrame);
325         assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
326                 mFrames.parentFrame);
327         assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
328                 mFrames.frame);
329     }
330 
331     @Test
layoutInDisplayCutoutModeDefaultWithLayoutInScreenAndLayoutInsetDecor()332     public void layoutInDisplayCutoutModeDefaultWithLayoutInScreenAndLayoutInsetDecor() {
333         addDisplayCutout();
334         mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
335         mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
336         mAttrs.setFitInsetsTypes(0);
337         computeFrames();
338 
339         assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.displayFrame);
340         assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.parentFrame);
341         assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.frame);
342     }
343 
344     @Test
layoutExtendedToDisplayCutout()345     public void layoutExtendedToDisplayCutout() {
346         addDisplayCutout();
347         final int height = DISPLAY_HEIGHT / 2;
348         mRequestedHeight = UNSPECIFIED_LENGTH;
349         mAttrs.height = height;
350         mAttrs.gravity = Gravity.TOP;
351         mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
352         mAttrs.setFitInsetsTypes(0);
353         mAttrs.privateFlags |= PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
354         computeFrames();
355 
356         assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mFrames.displayFrame);
357         assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mFrames.parentFrame);
358         assertRect(0, 0, DISPLAY_WIDTH, height + DISPLAY_CUTOUT_HEIGHT, mFrames.frame);
359     }
360 
361     @Test
layoutInDisplayCutoutModeDefaultWithInvisibleSystemBars()362     public void layoutInDisplayCutoutModeDefaultWithInvisibleSystemBars() {
363         addDisplayCutout();
364         mState.setSourceVisible(ID_STATUS_BAR, false);
365         mState.setSourceVisible(ID_NAVIGATION_BAR, false);
366         mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
367         mAttrs.setFitInsetsTypes(0);
368         computeFrames();
369 
370         assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
371                 mFrames.displayFrame);
372         assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
373                 mFrames.parentFrame);
374         assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
375                 mFrames.frame);
376     }
377 
378     @Test
layoutInDisplayCutoutModeShortEdges()379     public void layoutInDisplayCutoutModeShortEdges() {
380         addDisplayCutout();
381         mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
382         mAttrs.setFitInsetsTypes(0);
383         computeFrames();
384 
385         assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.displayFrame);
386         assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.parentFrame);
387         assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.frame);
388     }
389 
390     @Test
layoutInDisplayCutoutModeNever()391     public void layoutInDisplayCutoutModeNever() {
392         addDisplayCutout();
393         mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
394         mAttrs.setFitInsetsTypes(0);
395         computeFrames();
396 
397         assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
398                 mFrames.displayFrame);
399         assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
400                 mFrames.parentFrame);
401         assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
402                 mFrames.frame);
403     }
404 
405     @Test
layoutInDisplayCutoutModeAlways()406     public void layoutInDisplayCutoutModeAlways() {
407         addDisplayCutout();
408         mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
409         mAttrs.setFitInsetsTypes(0);
410         computeFrames();
411 
412         assertInsetByTopBottom(0, 0, mFrames.displayFrame);
413         assertInsetByTopBottom(0, 0, mFrames.parentFrame);
414         assertInsetByTopBottom(0, 0, mFrames.frame);
415     }
416 }
417