342 lines
16 KiB
Java
342 lines
16 KiB
Java
/*
|
|
* Copyright (C) 2021 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package android.view;
|
|
|
|
import static android.view.InsetsSource.ID_IME;
|
|
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
|
import static android.view.WindowInsets.Type.navigationBars;
|
|
import static android.view.WindowInsets.Type.systemBars;
|
|
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
|
|
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
|
|
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
|
|
import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
|
|
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
|
|
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
|
|
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
|
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
|
|
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
|
|
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
|
|
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
|
|
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
|
|
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
|
|
|
|
import android.app.WindowConfiguration;
|
|
import android.app.WindowConfiguration.WindowingMode;
|
|
import android.graphics.Insets;
|
|
import android.graphics.Point;
|
|
import android.graphics.Rect;
|
|
import android.util.Log;
|
|
import android.view.WindowInsets.Type.InsetsType;
|
|
import android.window.ClientWindowFrames;
|
|
|
|
/**
|
|
* Computes window frames.
|
|
* @hide
|
|
*/
|
|
public class WindowLayout {
|
|
private static final String TAG = WindowLayout.class.getSimpleName();
|
|
private static final boolean DEBUG = false;
|
|
|
|
public static final int UNSPECIFIED_LENGTH = -1;
|
|
|
|
/** These coordinates are the borders of the window layout. */
|
|
static final int MIN_X = -100000;
|
|
static final int MIN_Y = -100000;
|
|
static final int MAX_X = 100000;
|
|
static final int MAX_Y = 100000;
|
|
|
|
private final Rect mTempDisplayCutoutSafeExceptMaybeBarsRect = new Rect();
|
|
private final Rect mTempRect = new Rect();
|
|
|
|
public void computeFrames(WindowManager.LayoutParams attrs, InsetsState state,
|
|
Rect displayCutoutSafe, Rect windowBounds, @WindowingMode int windowingMode,
|
|
int requestedWidth, int requestedHeight, @InsetsType int requestedVisibleTypes,
|
|
float compatScale, ClientWindowFrames frames) {
|
|
final int type = attrs.type;
|
|
final int fl = attrs.flags;
|
|
final int pfl = attrs.privateFlags;
|
|
final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
|
|
final Rect attachedWindowFrame = frames.attachedFrame;
|
|
final Rect outDisplayFrame = frames.displayFrame;
|
|
final Rect outParentFrame = frames.parentFrame;
|
|
final Rect outFrame = frames.frame;
|
|
|
|
// Compute bounds restricted by insets
|
|
final Insets insets = state.calculateInsets(windowBounds, attrs.getFitInsetsTypes(),
|
|
attrs.isFitInsetsIgnoringVisibility());
|
|
final @WindowInsets.Side.InsetsSide int sides = attrs.getFitInsetsSides();
|
|
final int left = (sides & WindowInsets.Side.LEFT) != 0 ? insets.left : 0;
|
|
final int top = (sides & WindowInsets.Side.TOP) != 0 ? insets.top : 0;
|
|
final int right = (sides & WindowInsets.Side.RIGHT) != 0 ? insets.right : 0;
|
|
final int bottom = (sides & WindowInsets.Side.BOTTOM) != 0 ? insets.bottom : 0;
|
|
outDisplayFrame.set(windowBounds.left + left, windowBounds.top + top,
|
|
windowBounds.right - right, windowBounds.bottom - bottom);
|
|
|
|
if (attachedWindowFrame == null) {
|
|
outParentFrame.set(outDisplayFrame);
|
|
if ((pfl & PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME) != 0) {
|
|
final InsetsSource source = state.peekSource(ID_IME);
|
|
if (source != null) {
|
|
outParentFrame.inset(source.calculateInsets(
|
|
outParentFrame, false /* ignoreVisibility */));
|
|
}
|
|
}
|
|
} else {
|
|
outParentFrame.set(!layoutInScreen ? attachedWindowFrame : outDisplayFrame);
|
|
}
|
|
|
|
// Compute bounds restricted by display cutout
|
|
final int cutoutMode = attrs.layoutInDisplayCutoutMode;
|
|
final DisplayCutout cutout = state.getDisplayCutout();
|
|
final Rect displayCutoutSafeExceptMaybeBars = mTempDisplayCutoutSafeExceptMaybeBarsRect;
|
|
displayCutoutSafeExceptMaybeBars.set(displayCutoutSafe);
|
|
frames.isParentFrameClippedByDisplayCutout = false;
|
|
if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS && !cutout.isEmpty()) {
|
|
// Ensure that windows with a non-ALWAYS display cutout mode are laid out in
|
|
// the cutout safe zone.
|
|
final Rect displayFrame = state.getDisplayFrame();
|
|
if (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES) {
|
|
if (displayFrame.width() < displayFrame.height()) {
|
|
displayCutoutSafeExceptMaybeBars.top = MIN_Y;
|
|
displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
|
|
} else {
|
|
displayCutoutSafeExceptMaybeBars.left = MIN_X;
|
|
displayCutoutSafeExceptMaybeBars.right = MAX_X;
|
|
}
|
|
}
|
|
final boolean layoutInsetDecor = (attrs.flags & FLAG_LAYOUT_INSET_DECOR) != 0;
|
|
if (layoutInScreen && layoutInsetDecor
|
|
&& (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
|
|
|| cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) {
|
|
final Insets systemBarsInsets = state.calculateInsets(
|
|
displayFrame, systemBars(), requestedVisibleTypes);
|
|
if (systemBarsInsets.left >= cutout.getSafeInsetLeft()) {
|
|
displayCutoutSafeExceptMaybeBars.left = MIN_X;
|
|
}
|
|
if (systemBarsInsets.top >= cutout.getSafeInsetTop()) {
|
|
displayCutoutSafeExceptMaybeBars.top = MIN_Y;
|
|
}
|
|
if (systemBarsInsets.right >= cutout.getSafeInsetRight()) {
|
|
displayCutoutSafeExceptMaybeBars.right = MAX_X;
|
|
}
|
|
if (systemBarsInsets.bottom >= cutout.getSafeInsetBottom()) {
|
|
displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
|
|
}
|
|
}
|
|
if (type == TYPE_INPUT_METHOD
|
|
&& displayCutoutSafeExceptMaybeBars.bottom != MAX_Y
|
|
&& state.calculateInsets(displayFrame, navigationBars(), true).bottom > 0) {
|
|
// The IME can always extend under the bottom cutout if the navbar is there.
|
|
displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
|
|
}
|
|
final boolean attachedInParent = attachedWindowFrame != null && !layoutInScreen;
|
|
|
|
// TYPE_BASE_APPLICATION windows are never considered floating here because they don't
|
|
// get cropped / shifted to the displayFrame in WindowState.
|
|
final boolean floatingInScreenWindow = !attrs.isFullscreen() && layoutInScreen
|
|
&& type != TYPE_BASE_APPLICATION;
|
|
|
|
// Windows that are attached to a parent and laid out in said parent already avoid
|
|
// the cutout according to that parent and don't need to be further constrained.
|
|
// Floating IN_SCREEN windows get what they ask for and lay out in the full screen.
|
|
// They will later be cropped or shifted using the displayFrame in WindowState,
|
|
// which prevents overlap with the DisplayCutout.
|
|
if (!attachedInParent && !floatingInScreenWindow) {
|
|
mTempRect.set(outParentFrame);
|
|
outParentFrame.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
|
|
frames.isParentFrameClippedByDisplayCutout = !mTempRect.equals(outParentFrame);
|
|
}
|
|
outDisplayFrame.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
|
|
}
|
|
|
|
final boolean noLimits = (attrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;
|
|
final boolean inMultiWindowMode = WindowConfiguration.inMultiWindowMode(windowingMode);
|
|
|
|
// TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it.
|
|
// Also, we don't allow windows in multi-window mode to extend out of the screen.
|
|
if (noLimits && type != TYPE_SYSTEM_ERROR && !inMultiWindowMode) {
|
|
outDisplayFrame.left = MIN_X;
|
|
outDisplayFrame.top = MIN_Y;
|
|
outDisplayFrame.right = MAX_X;
|
|
outDisplayFrame.bottom = MAX_Y;
|
|
}
|
|
|
|
final boolean hasCompatScale = compatScale != 1f;
|
|
final int pw = outParentFrame.width();
|
|
final int ph = outParentFrame.height();
|
|
final boolean extendedByCutout =
|
|
(attrs.privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0;
|
|
int rw = requestedWidth;
|
|
int rh = requestedHeight;
|
|
float x, y;
|
|
int w, h;
|
|
|
|
// If the view hierarchy hasn't been measured, the requested width and height would be
|
|
// UNSPECIFIED_LENGTH. This can happen in the first layout of a window or in the simulated
|
|
// layout. If extendedByCutout is true, we cannot use the requested lengths. Otherwise,
|
|
// the window frame might be extended again because the requested lengths may come from the
|
|
// window frame.
|
|
if (rw == UNSPECIFIED_LENGTH || extendedByCutout) {
|
|
rw = attrs.width >= 0 ? attrs.width : pw;
|
|
}
|
|
if (rh == UNSPECIFIED_LENGTH || extendedByCutout) {
|
|
rh = attrs.height >= 0 ? attrs.height : ph;
|
|
}
|
|
|
|
if ((attrs.flags & FLAG_SCALED) != 0) {
|
|
if (attrs.width < 0) {
|
|
w = pw;
|
|
} else if (hasCompatScale) {
|
|
w = (int) (attrs.width * compatScale + .5f);
|
|
} else {
|
|
w = attrs.width;
|
|
}
|
|
if (attrs.height < 0) {
|
|
h = ph;
|
|
} else if (hasCompatScale) {
|
|
h = (int) (attrs.height * compatScale + .5f);
|
|
} else {
|
|
h = attrs.height;
|
|
}
|
|
} else {
|
|
if (attrs.width == MATCH_PARENT) {
|
|
w = pw;
|
|
} else if (hasCompatScale) {
|
|
w = (int) (rw * compatScale + .5f);
|
|
} else {
|
|
w = rw;
|
|
}
|
|
if (attrs.height == MATCH_PARENT) {
|
|
h = ph;
|
|
} else if (hasCompatScale) {
|
|
h = (int) (rh * compatScale + .5f);
|
|
} else {
|
|
h = rh;
|
|
}
|
|
}
|
|
|
|
if (hasCompatScale) {
|
|
x = attrs.x * compatScale;
|
|
y = attrs.y * compatScale;
|
|
} else {
|
|
x = attrs.x;
|
|
y = attrs.y;
|
|
}
|
|
|
|
if (inMultiWindowMode
|
|
&& (attrs.privateFlags & PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME) == 0) {
|
|
// Make sure window fits in parent frame since it is in a non-fullscreen task as
|
|
// required by {@link Gravity#apply} call.
|
|
w = Math.min(w, pw);
|
|
h = Math.min(h, ph);
|
|
}
|
|
|
|
// We need to fit it to the display if either
|
|
// a) The window is in a fullscreen container, or we don't have a task (we assume fullscreen
|
|
// for the taskless windows)
|
|
// b) If it's a secondary app window, we also need to fit it to the display unless
|
|
// FLAG_LAYOUT_NO_LIMITS is set. This is so we place Popups, dialogs, and similar windows on
|
|
// screen, but SurfaceViews want to be always at a specific location so we don't fit it to
|
|
// the display.
|
|
final boolean fitToDisplay = !inMultiWindowMode
|
|
|| ((attrs.type != TYPE_BASE_APPLICATION) && !noLimits);
|
|
|
|
// Set mFrame
|
|
Gravity.apply(attrs.gravity, w, h, outParentFrame,
|
|
(int) (x + attrs.horizontalMargin * pw),
|
|
(int) (y + attrs.verticalMargin * ph), outFrame);
|
|
|
|
// Now make sure the window fits in the overall display frame.
|
|
if (fitToDisplay) {
|
|
Gravity.applyDisplay(attrs.gravity, outDisplayFrame, outFrame);
|
|
}
|
|
|
|
if (extendedByCutout) {
|
|
extendFrameByCutout(displayCutoutSafe, outDisplayFrame, outFrame,
|
|
mTempRect);
|
|
}
|
|
|
|
if (DEBUG) Log.d(TAG, "computeFrames " + attrs.getTitle()
|
|
+ " frames=" + frames
|
|
+ " windowBounds=" + windowBounds.toShortString()
|
|
+ " requestedWidth=" + requestedWidth
|
|
+ " requestedHeight=" + requestedHeight
|
|
+ " compatScale=" + compatScale
|
|
+ " windowingMode=" + WindowConfiguration.windowingModeToString(windowingMode)
|
|
+ " displayCutoutSafe=" + displayCutoutSafe
|
|
+ " attrs=" + attrs
|
|
+ " state=" + state
|
|
+ " requestedInvisibleTypes=" + WindowInsets.Type.toString(~requestedVisibleTypes));
|
|
}
|
|
|
|
public static void extendFrameByCutout(Rect displayCutoutSafe,
|
|
Rect displayFrame, Rect inOutFrame, Rect tempRect) {
|
|
if (displayCutoutSafe.contains(inOutFrame)) {
|
|
return;
|
|
}
|
|
tempRect.set(inOutFrame);
|
|
|
|
// Move the frame into displayCutoutSafe.
|
|
Gravity.applyDisplay(0 /* gravity */, displayCutoutSafe, tempRect);
|
|
|
|
if (tempRect.intersect(displayFrame)) {
|
|
inOutFrame.union(tempRect);
|
|
}
|
|
}
|
|
|
|
public static void computeSurfaceSize(WindowManager.LayoutParams attrs, Rect maxBounds,
|
|
int requestedWidth, int requestedHeight, Rect winFrame, boolean dragResizing,
|
|
Point outSurfaceSize) {
|
|
int width;
|
|
int height;
|
|
if ((attrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0) {
|
|
// For a scaled surface, we always want the requested size.
|
|
width = requestedWidth;
|
|
height = requestedHeight;
|
|
} else {
|
|
// When we're doing a drag-resizing, request a surface that's fullscreen size,
|
|
// so that we don't need to reallocate during the process. This also prevents
|
|
// buffer drops due to size mismatch.
|
|
if (dragResizing) {
|
|
// The maxBounds should match the display size which applies fixed-rotation
|
|
// transformation if there is any.
|
|
width = maxBounds.width();
|
|
height = maxBounds.height();
|
|
} else {
|
|
width = winFrame.width();
|
|
height = winFrame.height();
|
|
}
|
|
}
|
|
|
|
// This doesn't necessarily mean that there is an error in the system. The sizes might be
|
|
// incorrect, because it is before the first layout or draw.
|
|
if (width < 1) {
|
|
width = 1;
|
|
}
|
|
if (height < 1) {
|
|
height = 1;
|
|
}
|
|
|
|
// Adjust for surface insets.
|
|
final Rect surfaceInsets = attrs.surfaceInsets;
|
|
width += surfaceInsets.left + surfaceInsets.right;
|
|
height += surfaceInsets.top + surfaceInsets.bottom;
|
|
|
|
outSurfaceSize.set(width, height);
|
|
}
|
|
}
|