• 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                 outParentFrame.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
161                 frames.isParentFrameClippedByDisplayCutout = !mTempRect.equals(outParentFrame);
162             }
163             outDisplayFrame.intersectUnchecked(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 
extendFrameByCutout(Rect displayCutoutSafe, Rect displayFrame, Rect inOutFrame, Rect tempRect)286     public static void extendFrameByCutout(Rect displayCutoutSafe,
287             Rect displayFrame, Rect inOutFrame, Rect tempRect) {
288         if (displayCutoutSafe.contains(inOutFrame)) {
289             return;
290         }
291         tempRect.set(inOutFrame);
292 
293         // Move the frame into displayCutoutSafe.
294         Gravity.applyDisplay(0 /* gravity */, displayCutoutSafe, tempRect);
295 
296         if (tempRect.intersect(displayFrame)) {
297             inOutFrame.union(tempRect);
298         }
299     }
300 
computeSurfaceSize(WindowManager.LayoutParams attrs, Rect maxBounds, int requestedWidth, int requestedHeight, Rect winFrame, boolean dragResizing, Point outSurfaceSize)301     public static void computeSurfaceSize(WindowManager.LayoutParams attrs, Rect maxBounds,
302             int requestedWidth, int requestedHeight, Rect winFrame, boolean dragResizing,
303             Point outSurfaceSize) {
304         int width;
305         int height;
306         if ((attrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0) {
307             // For a scaled surface, we always want the requested size.
308             width = requestedWidth;
309             height = requestedHeight;
310         } else {
311             // When we're doing a drag-resizing, request a surface that's fullscreen size,
312             // so that we don't need to reallocate during the process. This also prevents
313             // buffer drops due to size mismatch.
314             if (dragResizing) {
315                 // The maxBounds should match the display size which applies fixed-rotation
316                 // transformation if there is any.
317                 width = maxBounds.width();
318                 height = maxBounds.height();
319             } else {
320                 width = winFrame.width();
321                 height = winFrame.height();
322             }
323         }
324 
325         // This doesn't necessarily mean that there is an error in the system. The sizes might be
326         // incorrect, because it is before the first layout or draw.
327         if (width < 1) {
328             width = 1;
329         }
330         if (height < 1) {
331             height = 1;
332         }
333 
334         // Adjust for surface insets.
335         final Rect surfaceInsets = attrs.surfaceInsets;
336         width += surfaceInsets.left + surfaceInsets.right;
337         height += surfaceInsets.top + surfaceInsets.bottom;
338 
339         outSurfaceSize.set(width, height);
340     }
341 }
342