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