• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.ViewGroup.LayoutParams.MATCH_PARENT;
21 import static android.view.WindowInsets.Type.navigationBars;
22 import static android.view.WindowInsets.Type.systemBars;
23 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
24 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
25 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
26 import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
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_SHORT_EDGES;
30 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
31 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
32 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
33 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
34 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
35 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
36 
37 import android.app.WindowConfiguration;
38 import android.app.WindowConfiguration.WindowingMode;
39 import android.graphics.Insets;
40 import android.graphics.Point;
41 import android.graphics.Rect;
42 import android.util.Log;
43 import android.view.WindowInsets.Type.InsetsType;
44 import android.window.ClientWindowFrames;
45 
46 /**
47  * Computes window frames.
48  * @hide
49  */
50 public class WindowLayout {
51     private static final String TAG = WindowLayout.class.getSimpleName();
52     private static final boolean DEBUG = false;
53 
54     public static final int UNSPECIFIED_LENGTH = -1;
55 
56     /** These coordinates are the borders of the window layout. */
57     static final int MIN_X = -100000;
58     static final int MIN_Y = -100000;
59     static final int MAX_X = 100000;
60     static final int MAX_Y = 100000;
61 
62     private final Rect mTempDisplayCutoutSafeExceptMaybeBarsRect = new Rect();
63     private final Rect mTempRect = new Rect();
64 
computeFrames(WindowManager.LayoutParams attrs, InsetsState state, Rect displayCutoutSafe, Rect windowBounds, @WindowingMode int windowingMode, int requestedWidth, int requestedHeight, @InsetsType int requestedVisibleTypes, float compatScale, ClientWindowFrames frames)65     public void computeFrames(WindowManager.LayoutParams attrs, InsetsState state,
66             Rect displayCutoutSafe, Rect windowBounds, @WindowingMode int windowingMode,
67             int requestedWidth, int requestedHeight, @InsetsType int requestedVisibleTypes,
68             float compatScale, ClientWindowFrames frames) {
69         final int type = attrs.type;
70         final int fl = attrs.flags;
71         final int pfl = attrs.privateFlags;
72         final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
73         final Rect attachedWindowFrame = frames.attachedFrame;
74         final Rect outDisplayFrame = frames.displayFrame;
75         final Rect outParentFrame = frames.parentFrame;
76         final Rect outFrame = frames.frame;
77 
78         // Compute bounds restricted by insets
79         final Insets insets = state.calculateInsets(windowBounds, attrs.getFitInsetsTypes(),
80                 attrs.isFitInsetsIgnoringVisibility());
81         final @WindowInsets.Side.InsetsSide int sides = attrs.getFitInsetsSides();
82         final int left = (sides & WindowInsets.Side.LEFT) != 0 ? insets.left : 0;
83         final int top = (sides & WindowInsets.Side.TOP) != 0 ? insets.top : 0;
84         final int right = (sides & WindowInsets.Side.RIGHT) != 0 ? insets.right : 0;
85         final int bottom = (sides & WindowInsets.Side.BOTTOM) != 0 ? insets.bottom : 0;
86         outDisplayFrame.set(windowBounds.left + left, windowBounds.top + top,
87                 windowBounds.right - right, windowBounds.bottom - bottom);
88 
89         if (attachedWindowFrame == null) {
90             outParentFrame.set(outDisplayFrame);
91             if ((pfl & PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME) != 0) {
92                 final InsetsSource source = state.peekSource(ID_IME);
93                 if (source != null) {
94                     outParentFrame.inset(source.calculateInsets(
95                             outParentFrame, false /* ignoreVisibility */));
96                 }
97             }
98         } else {
99             outParentFrame.set(!layoutInScreen ? attachedWindowFrame : outDisplayFrame);
100         }
101 
102         // Compute bounds restricted by display cutout
103         final int cutoutMode = attrs.layoutInDisplayCutoutMode;
104         final DisplayCutout cutout = state.getDisplayCutout();
105         final Rect displayCutoutSafeExceptMaybeBars = mTempDisplayCutoutSafeExceptMaybeBarsRect;
106         displayCutoutSafeExceptMaybeBars.set(displayCutoutSafe);
107         frames.isParentFrameClippedByDisplayCutout = false;
108         if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS && !cutout.isEmpty()) {
109             // Ensure that windows with a non-ALWAYS display cutout mode are laid out in
110             // the cutout safe zone.
111             final Rect displayFrame = state.getDisplayFrame();
112             if (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES) {
113                 if (displayFrame.width() < displayFrame.height()) {
114                     displayCutoutSafeExceptMaybeBars.top = MIN_Y;
115                     displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
116                 } else {
117                     displayCutoutSafeExceptMaybeBars.left = MIN_X;
118                     displayCutoutSafeExceptMaybeBars.right = MAX_X;
119                 }
120             }
121             final boolean layoutInsetDecor = (attrs.flags & FLAG_LAYOUT_INSET_DECOR) != 0;
122             if (layoutInScreen && layoutInsetDecor
123                     && (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
124                     || cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) {
125                 final Insets systemBarsInsets = state.calculateInsets(
126                         displayFrame, systemBars(), requestedVisibleTypes);
127                 if (systemBarsInsets.left >= cutout.getSafeInsetLeft()) {
128                     displayCutoutSafeExceptMaybeBars.left = MIN_X;
129                 }
130                 if (systemBarsInsets.top >= cutout.getSafeInsetTop()) {
131                     displayCutoutSafeExceptMaybeBars.top = MIN_Y;
132                 }
133                 if (systemBarsInsets.right >= cutout.getSafeInsetRight()) {
134                     displayCutoutSafeExceptMaybeBars.right = MAX_X;
135                 }
136                 if (systemBarsInsets.bottom >= cutout.getSafeInsetBottom()) {
137                     displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
138                 }
139             }
140             if (type == TYPE_INPUT_METHOD
141                     && displayCutoutSafeExceptMaybeBars.bottom != MAX_Y
142                     && state.calculateInsets(displayFrame, navigationBars(), true).bottom > 0) {
143                 // The IME can always extend under the bottom cutout if the navbar is there.
144                 displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
145             }
146             final boolean attachedInParent = attachedWindowFrame != null && !layoutInScreen;
147 
148             // TYPE_BASE_APPLICATION windows are never considered floating here because they don't
149             // get cropped / shifted to the displayFrame in WindowState.
150             final boolean floatingInScreenWindow = !attrs.isFullscreen() && layoutInScreen
151                     && type != TYPE_BASE_APPLICATION;
152 
153             // Windows that are attached to a parent and laid out in said parent already avoid
154             // the cutout according to that parent and don't need to be further constrained.
155             // Floating IN_SCREEN windows get what they ask for and lay out in the full screen.
156             // They will later be cropped or shifted using the displayFrame in WindowState,
157             // which prevents overlap with the DisplayCutout.
158             if (!attachedInParent && !floatingInScreenWindow) {
159                 mTempRect.set(outParentFrame);
160                 intersectOrClamp(outParentFrame, displayCutoutSafeExceptMaybeBars);
161                 frames.isParentFrameClippedByDisplayCutout = !mTempRect.equals(outParentFrame);
162             }
163             intersectOrClamp(outDisplayFrame, displayCutoutSafeExceptMaybeBars);
164         }
165 
166         final boolean noLimits = (attrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;
167         final boolean inMultiWindowMode = WindowConfiguration.inMultiWindowMode(windowingMode);
168 
169         // TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it.
170         // Also, we don't allow windows in multi-window mode to extend out of the screen.
171         if (noLimits && type != TYPE_SYSTEM_ERROR && !inMultiWindowMode) {
172             outDisplayFrame.left = MIN_X;
173             outDisplayFrame.top = MIN_Y;
174             outDisplayFrame.right = MAX_X;
175             outDisplayFrame.bottom = MAX_Y;
176         }
177 
178         final boolean hasCompatScale = compatScale != 1f;
179         final int pw = outParentFrame.width();
180         final int ph = outParentFrame.height();
181         final boolean extendedByCutout =
182                 (attrs.privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0;
183         int rw = requestedWidth;
184         int rh = requestedHeight;
185         float x, y;
186         int w, h;
187 
188         // If the view hierarchy hasn't been measured, the requested width and height would be
189         // UNSPECIFIED_LENGTH. This can happen in the first layout of a window or in the simulated
190         // layout. If extendedByCutout is true, we cannot use the requested lengths. Otherwise,
191         // the window frame might be extended again because the requested lengths may come from the
192         // window frame.
193         if (rw == UNSPECIFIED_LENGTH || extendedByCutout) {
194             rw = attrs.width >= 0 ? attrs.width : pw;
195         }
196         if (rh == UNSPECIFIED_LENGTH || extendedByCutout) {
197             rh = attrs.height >= 0 ? attrs.height : ph;
198         }
199 
200         if ((attrs.flags & FLAG_SCALED) != 0) {
201             if (attrs.width < 0) {
202                 w = pw;
203             } else if (hasCompatScale) {
204                 w = (int) (attrs.width * compatScale + .5f);
205             } else {
206                 w = attrs.width;
207             }
208             if (attrs.height < 0) {
209                 h = ph;
210             } else if (hasCompatScale) {
211                 h = (int) (attrs.height * compatScale + .5f);
212             } else {
213                 h = attrs.height;
214             }
215         } else {
216             if (attrs.width == MATCH_PARENT) {
217                 w = pw;
218             } else if (hasCompatScale) {
219                 w = (int) (rw * compatScale + .5f);
220             } else {
221                 w = rw;
222             }
223             if (attrs.height == MATCH_PARENT) {
224                 h = ph;
225             } else if (hasCompatScale) {
226                 h = (int) (rh * compatScale + .5f);
227             } else {
228                 h = rh;
229             }
230         }
231 
232         if (hasCompatScale) {
233             x = attrs.x * compatScale;
234             y = attrs.y * compatScale;
235         } else {
236             x = attrs.x;
237             y = attrs.y;
238         }
239 
240         if (inMultiWindowMode
241                 && (attrs.privateFlags & PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME) == 0) {
242             // Make sure window fits in parent frame since it is in a non-fullscreen task as
243             // required by {@link Gravity#apply} call.
244             w = Math.min(w, pw);
245             h = Math.min(h, ph);
246         }
247 
248         // We need to fit it to the display if either
249         // a) The window is in a fullscreen container, or we don't have a task (we assume fullscreen
250         // for the taskless windows)
251         // b) If it's a secondary app window, we also need to fit it to the display unless
252         // FLAG_LAYOUT_NO_LIMITS is set. This is so we place Popups, dialogs, and similar windows on
253         // screen, but SurfaceViews want to be always at a specific location so we don't fit it to
254         // the display.
255         final boolean fitToDisplay = !inMultiWindowMode
256                 || ((attrs.type != TYPE_BASE_APPLICATION) && !noLimits);
257 
258         // Set mFrame
259         Gravity.apply(attrs.gravity, w, h, outParentFrame,
260                 (int) (x + attrs.horizontalMargin * pw),
261                 (int) (y + attrs.verticalMargin * ph), outFrame);
262 
263         // Now make sure the window fits in the overall display frame.
264         if (fitToDisplay) {
265             Gravity.applyDisplay(attrs.gravity, outDisplayFrame, outFrame);
266         }
267 
268         if (extendedByCutout) {
269             extendFrameByCutout(displayCutoutSafe, outDisplayFrame, outFrame,
270                     mTempRect);
271         }
272 
273         if (DEBUG) Log.d(TAG, "computeFrames " + attrs.getTitle()
274                 + " frames=" + frames
275                 + " windowBounds=" + windowBounds.toShortString()
276                 + " requestedWidth=" + requestedWidth
277                 + " requestedHeight=" + requestedHeight
278                 + " compatScale=" + compatScale
279                 + " windowingMode=" + WindowConfiguration.windowingModeToString(windowingMode)
280                 + " displayCutoutSafe=" + displayCutoutSafe
281                 + " attrs=" + attrs
282                 + " state=" + state
283                 + " requestedInvisibleTypes=" + WindowInsets.Type.toString(~requestedVisibleTypes));
284     }
285 
286     /**
287      * If both rectangles intersect, set inOutRect to that intersection. Otherwise, clamp inOutRect
288      * to the side (or the corner) that the other rectangle is away from.
289      * Unlike {@link Rect#intersectUnchecked(Rect)}, this method guarantees that the new rectangle
290      * is valid and contained in inOutRect if rectangles involved are valid.
291      */
intersectOrClamp(Rect inOutRect, Rect other)292     private static void intersectOrClamp(Rect inOutRect, Rect other) {
293         inOutRect.left = Math.min(Math.max(inOutRect.left, other.left), inOutRect.right);
294         inOutRect.top = Math.min(Math.max(inOutRect.top, other.top), inOutRect.bottom);
295         inOutRect.right = Math.max(Math.min(inOutRect.right, other.right), inOutRect.left);
296         inOutRect.bottom = Math.max(Math.min(inOutRect.bottom, other.bottom), inOutRect.top);
297     }
298 
extendFrameByCutout(Rect displayCutoutSafe, Rect displayFrame, Rect inOutFrame, Rect tempRect)299     public static void extendFrameByCutout(Rect displayCutoutSafe,
300             Rect displayFrame, Rect inOutFrame, Rect tempRect) {
301         if (displayCutoutSafe.contains(inOutFrame)) {
302             return;
303         }
304         tempRect.set(inOutFrame);
305 
306         // Move the frame into displayCutoutSafe.
307         Gravity.applyDisplay(0 /* gravity */, displayCutoutSafe, tempRect);
308 
309         if (tempRect.intersect(displayFrame)) {
310             inOutFrame.union(tempRect);
311         }
312     }
313 
computeSurfaceSize(WindowManager.LayoutParams attrs, Rect maxBounds, int requestedWidth, int requestedHeight, Rect winFrame, boolean dragResizing, Point outSurfaceSize)314     public static void computeSurfaceSize(WindowManager.LayoutParams attrs, Rect maxBounds,
315             int requestedWidth, int requestedHeight, Rect winFrame, boolean dragResizing,
316             Point outSurfaceSize) {
317         int width;
318         int height;
319         if ((attrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0) {
320             // For a scaled surface, we always want the requested size.
321             width = requestedWidth;
322             height = requestedHeight;
323         } else {
324             // When we're doing a drag-resizing, request a surface that's fullscreen size,
325             // so that we don't need to reallocate during the process. This also prevents
326             // buffer drops due to size mismatch.
327             if (dragResizing) {
328                 // The maxBounds should match the display size which applies fixed-rotation
329                 // transformation if there is any.
330                 width = maxBounds.width();
331                 height = maxBounds.height();
332             } else {
333                 width = winFrame.width();
334                 height = winFrame.height();
335             }
336         }
337 
338         // This doesn't necessarily mean that there is an error in the system. The sizes might be
339         // incorrect, because it is before the first layout or draw.
340         if (width < 1) {
341             width = 1;
342         }
343         if (height < 1) {
344             height = 1;
345         }
346 
347         // Adjust for surface insets.
348         final Rect surfaceInsets = attrs.surfaceInsets;
349         width += surfaceInsets.left + surfaceInsets.right;
350         height += surfaceInsets.top + surfaceInsets.bottom;
351 
352         outSurfaceSize.set(width, height);
353     }
354 }
355