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