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