summaryrefslogtreecommitdiff
path: root/core/java/android/view/DisplayCutout.java
diff options
context:
space:
mode:
authorAdrian Roos <roosa@google.com>2018-03-05 19:50:16 +0100
committerAdrian Roos <roosa@google.com>2018-03-12 17:26:01 +0000
commit6a4fa0ec183e20c32e7816f5475e72fa9126356c (patch)
treeceedfd2e6e9fd274bb87d5a05472e8ba01f6a875 /core/java/android/view/DisplayCutout.java
parent3a78ec6ae4d44666f02c3ea8e543132aaa59e831 (diff)
DisplayCutout: Support more than one cutout
Also makes API more restrictive. Also moves window manager specific logic out of the framework. Also fixes SystemUI such that it can properly deal with more than one cutout. Bug: 74195186 Test: atest DisplayCutoutTest WmDisplayCutoutTest DisplayContentTests WindowFrameTests Change-Id: Ib7b89e119ce2d3961687579bb81eadce1159a600
Diffstat (limited to 'core/java/android/view/DisplayCutout.java')
-rw-r--r--core/java/android/view/DisplayCutout.java240
1 files changed, 118 insertions, 122 deletions
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index bb16afddcd63..b6adee9501a6 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -18,10 +18,6 @@ package android.view;
import static android.view.DisplayCutoutProto.BOUNDS;
import static android.view.DisplayCutoutProto.INSETS;
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_180;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
@@ -36,23 +32,24 @@ import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
import android.util.PathParser;
-import android.util.Size;
import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import java.util.Objects;
+import java.util.ArrayList;
+import java.util.List;
/**
- * Represents a part of the display that is not functional for displaying content.
+ * Represents the area of the display that is not functional for displaying content.
*
* <p>{@code DisplayCutout} is immutable.
*/
public final class DisplayCutout {
private static final String TAG = "DisplayCutout";
+ private static final String BOTTOM_MARKER = "@bottom";
private static final String DP_MARKER = "@dp";
/**
@@ -74,7 +71,7 @@ public final class DisplayCutout {
* @hide
*/
public static final DisplayCutout NO_CUTOUT = new DisplayCutout(ZERO_RECT, EMPTY_REGION,
- new Size(0, 0));
+ false /* copyArguments */);
private static final Object CACHE_LOCK = new Object();
@@ -89,38 +86,38 @@ public final class DisplayCutout {
private final Rect mSafeInsets;
private final Region mBounds;
- private final Size mFrameSize; // TODO: move frameSize, calculateRelativeTo, etc. into WM.
/**
* Creates a DisplayCutout instance.
*
* @param safeInsets the insets from each edge which avoid the display cutout as returned by
* {@link #getSafeInsetTop()} etc.
- * @param bounds the bounds of the display cutout as returned by {@link #getBounds()}.
+ * @param boundingRects the bounding rects of the display cutouts as returned by
+ * {@link #getBoundingRects()} ()}.
*/
// TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE)
- public DisplayCutout(Rect safeInsets, Region bounds) {
+ public DisplayCutout(Rect safeInsets, List<Rect> boundingRects) {
this(safeInsets != null ? new Rect(safeInsets) : ZERO_RECT,
- bounds != null ? Region.obtain(bounds) : Region.obtain(),
- null /* frameSize */);
+ boundingRectsToRegion(boundingRects),
+ true /* copyArguments */);
}
/**
* Creates a DisplayCutout instance.
*
- * NOTE: the Rects passed into this instance are not copied and MUST remain unchanged.
- *
- * @hide
+ * @param copyArguments if true, create a copy of the arguments. If false, the passed arguments
+ * are not copied and MUST remain unchanged forever.
*/
- @VisibleForTesting
- public DisplayCutout(Rect safeInsets, Region bounds, Size frameSize) {
- mSafeInsets = safeInsets != null ? safeInsets : ZERO_RECT;
- mBounds = bounds != null ? bounds : Region.obtain();
- mFrameSize = frameSize;
+ private DisplayCutout(Rect safeInsets, Region bounds, boolean copyArguments) {
+ mSafeInsets = safeInsets == null ? ZERO_RECT :
+ (copyArguments ? new Rect(safeInsets) : safeInsets);
+ mBounds = bounds == null ? Region.obtain() :
+ (copyArguments ? Region.obtain(bounds) : bounds);
}
/**
- * Returns true if there is no cutout or it is outside of the content view.
+ * Returns true if the safe insets are empty (and therefore the current view does not
+ * overlap with the cutout or cutout area).
*
* @hide
*/
@@ -128,6 +125,15 @@ public final class DisplayCutout {
return mSafeInsets.equals(ZERO_RECT);
}
+ /**
+ * Returns true if there is no cutout, i.e. the bounds are empty.
+ *
+ * @hide
+ */
+ public boolean isBoundsEmpty() {
+ return mBounds.isEmpty();
+ }
+
/** Returns the inset from the top which avoids the display cutout in pixels. */
public int getSafeInsetTop() {
return mSafeInsets.top;
@@ -161,23 +167,60 @@ public final class DisplayCutout {
/**
* Returns the bounding region of the cutout.
*
+ * <p>
+ * <strong>Note:</strong> There may be more than one cutout, in which case the returned
+ * {@code Region} will be non-contiguous and its bounding rect will be meaningless without
+ * intersecting it first.
+ *
+ * Example:
+ * <pre>
+ * // Getting the bounding rectangle of the top display cutout
+ * Region bounds = displayCutout.getBounds();
+ * bounds.op(0, 0, Integer.MAX_VALUE, displayCutout.getSafeInsetTop(), Region.Op.INTERSECT);
+ * Rect topDisplayCutout = bounds.getBoundingRect();
+ * </pre>
+ *
* @return the bounding region of the cutout. Coordinates are relative
* to the top-left corner of the content view and in pixel units.
+ * @hide
*/
public Region getBounds() {
return Region.obtain(mBounds);
}
/**
- * Returns the bounding rect of the cutout.
+ * Returns a list of {@code Rect}s, each of which is the bounding rectangle for a non-functional
+ * area on the display.
*
- * @return the bounding rect of the cutout. Coordinates are relative
- * to the top-left corner of the content view.
- * @hide
+ * There will be at most one non-functional area per short edge of the device, and none on
+ * the long edges.
+ *
+ * @return a list of bounding {@code Rect}s, one for each display cutout area.
*/
- public Rect getBoundingRect() {
- // TODO(roosa): Inline.
- return mBounds.getBounds();
+ public List<Rect> getBoundingRects() {
+ List<Rect> result = new ArrayList<>();
+ Region bounds = Region.obtain();
+ // top
+ bounds.set(mBounds);
+ bounds.op(0, 0, Integer.MAX_VALUE, getSafeInsetTop(), Region.Op.INTERSECT);
+ if (!bounds.isEmpty()) {
+ result.add(bounds.getBounds());
+ }
+ // left
+ bounds.set(mBounds);
+ bounds.op(0, 0, getSafeInsetLeft(), Integer.MAX_VALUE, Region.Op.INTERSECT);
+ if (!bounds.isEmpty()) {
+ result.add(bounds.getBounds());
+ }
+ // right & bottom
+ bounds.set(mBounds);
+ bounds.op(getSafeInsetLeft() + 1, getSafeInsetTop() + 1,
+ Integer.MAX_VALUE, Integer.MAX_VALUE, Region.Op.INTERSECT);
+ if (!bounds.isEmpty()) {
+ result.add(bounds.getBounds());
+ }
+ bounds.recycle();
+ return result;
}
@Override
@@ -195,8 +238,7 @@ public final class DisplayCutout {
if (o instanceof DisplayCutout) {
DisplayCutout c = (DisplayCutout) o;
return mSafeInsets.equals(c.mSafeInsets)
- && mBounds.equals(c.mBounds)
- && Objects.equals(mFrameSize, c.mFrameSize);
+ && mBounds.equals(c.mBounds);
}
return false;
}
@@ -204,7 +246,7 @@ public final class DisplayCutout {
@Override
public String toString() {
return "DisplayCutout{insets=" + mSafeInsets
- + " boundingRect=" + getBoundingRect()
+ + " boundingRect=" + mBounds.getBounds()
+ "}";
}
@@ -249,88 +291,19 @@ public final class DisplayCutout {
}
bounds.translate(-insetLeft, -insetTop);
- Size frame = mFrameSize == null ? null : new Size(
- mFrameSize.getWidth() - insetLeft - insetRight,
- mFrameSize.getHeight() - insetTop - insetBottom);
-
- return new DisplayCutout(safeInsets, bounds, frame);
+ return new DisplayCutout(safeInsets, bounds, false /* copyArguments */);
}
/**
- * Recalculates the cutout relative to the given reference frame.
- *
- * The safe insets must already have been computed, e.g. with {@link #computeSafeInsets}.
+ * Returns a copy of this instance with the safe insets replaced with the parameter.
*
- * @return a copy of this instance with the safe insets recalculated
- * @hide
- */
- public DisplayCutout calculateRelativeTo(Rect frame) {
- return inset(frame.left, frame.top,
- mFrameSize.getWidth() - frame.right, mFrameSize.getHeight() - frame.bottom);
- }
-
- /**
- * Calculates the safe insets relative to the given display size.
+ * @param safeInsets the new safe insets in pixels
+ * @return a copy of this instance with the safe insets replaced with the argument.
*
- * @return a copy of this instance with the safe insets calculated
* @hide
*/
- public DisplayCutout computeSafeInsets(int width, int height) {
- if (this == NO_CUTOUT || mBounds.isEmpty()) {
- return NO_CUTOUT;
- }
-
- return computeSafeInsets(new Size(width, height), mBounds);
- }
-
- private static DisplayCutout computeSafeInsets(Size displaySize, Region bounds) {
- Rect boundingRect = bounds.getBounds();
- Rect safeRect = new Rect();
-
- int bestArea = 0;
- int bestVariant = 0;
- for (int variant = ROTATION_0; variant <= ROTATION_270; variant++) {
- int area = calculateInsetVariantArea(displaySize, boundingRect, variant, safeRect);
- if (bestArea < area) {
- bestArea = area;
- bestVariant = variant;
- }
- }
- calculateInsetVariantArea(displaySize, boundingRect, bestVariant, safeRect);
- if (safeRect.isEmpty()) {
- // The entire displaySize overlaps with the cutout.
- safeRect.set(0, displaySize.getHeight(), 0, 0);
- } else {
- // Convert safeRect to insets relative to displaySize. We're reusing the rect here to
- // avoid an allocation.
- safeRect.set(
- Math.max(0, safeRect.left),
- Math.max(0, safeRect.top),
- Math.max(0, displaySize.getWidth() - safeRect.right),
- Math.max(0, displaySize.getHeight() - safeRect.bottom));
- }
-
- return new DisplayCutout(safeRect, bounds, displaySize);
- }
-
- private static int calculateInsetVariantArea(Size display, Rect boundingRect, int variant,
- Rect outSafeRect) {
- switch (variant) {
- case ROTATION_0:
- outSafeRect.set(0, 0, display.getWidth(), boundingRect.top);
- break;
- case ROTATION_90:
- outSafeRect.set(0, 0, boundingRect.left, display.getHeight());
- break;
- case ROTATION_180:
- outSafeRect.set(0, boundingRect.bottom, display.getWidth(), display.getHeight());
- break;
- case ROTATION_270:
- outSafeRect.set(boundingRect.right, 0, display.getWidth(), display.getHeight());
- break;
- }
-
- return outSafeRect.isEmpty() ? 0 : outSafeRect.width() * outSafeRect.height();
+ public DisplayCutout replaceSafeInsets(Rect safeInsets) {
+ return new DisplayCutout(new Rect(safeInsets), mBounds, false /* copyArguments */);
}
private static int atLeastZero(int value) {
@@ -369,7 +342,7 @@ public final class DisplayCutout {
Region bounds = new Region();
bounds.setPath(path, clipRegion);
clipRegion.recycle();
- return new DisplayCutout(ZERO_RECT, bounds, null /* frameSize */);
+ return new DisplayCutout(ZERO_RECT, bounds, false /* copyArguments */);
}
/**
@@ -377,9 +350,9 @@ public final class DisplayCutout {
*
* @hide
*/
- public static DisplayCutout fromResources(Resources res, int displayWidth) {
+ public static DisplayCutout fromResources(Resources res, int displayWidth, int displayHeight) {
return fromSpec(res.getString(R.string.config_mainBuiltInDisplayCutout),
- displayWidth, res.getDisplayMetrics().density);
+ displayWidth, displayHeight, res.getDisplayMetrics().density);
}
/**
@@ -388,7 +361,8 @@ public final class DisplayCutout {
* @hide
*/
@VisibleForTesting(visibility = PRIVATE)
- public static DisplayCutout fromSpec(String spec, int displayWidth, float density) {
+ public static DisplayCutout fromSpec(String spec, int displayWidth, int displayHeight,
+ float density) {
if (TextUtils.isEmpty(spec)) {
return null;
}
@@ -404,7 +378,14 @@ public final class DisplayCutout {
spec = spec.substring(0, spec.length() - DP_MARKER.length());
}
- Path p;
+ String bottomSpec = null;
+ if (spec.contains(BOTTOM_MARKER)) {
+ String[] splits = spec.split(BOTTOM_MARKER, 2);
+ spec = splits[0].trim();
+ bottomSpec = splits[1].trim();
+ }
+
+ final Path p;
try {
p = PathParser.createPathFromPathData(spec);
} catch (Throwable e) {
@@ -419,6 +400,20 @@ public final class DisplayCutout {
m.postTranslate(displayWidth / 2f, 0);
p.transform(m);
+ if (bottomSpec != null) {
+ final Path bottomPath;
+ try {
+ bottomPath = PathParser.createPathFromPathData(bottomSpec);
+ } catch (Throwable e) {
+ Log.wtf(TAG, "Could not inflate bottom cutout: ", e);
+ return null;
+ }
+ // Keep top transform
+ m.postTranslate(0, displayHeight);
+ bottomPath.transform(m);
+ p.addPath(bottomPath);
+ }
+
final DisplayCutout result = fromBounds(p);
synchronized (CACHE_LOCK) {
sCachedSpec = spec;
@@ -429,6 +424,16 @@ public final class DisplayCutout {
return result;
}
+ private static Region boundingRectsToRegion(List<Rect> rects) {
+ Region result = Region.obtain();
+ if (rects != null) {
+ for (Rect r : rects) {
+ result.op(r, Region.Op.UNION);
+ }
+ }
+ return result;
+ }
+
/**
* Helper class for passing {@link DisplayCutout} through binder.
*
@@ -472,12 +477,6 @@ public final class DisplayCutout {
out.writeInt(1);
out.writeTypedObject(cutout.mSafeInsets, flags);
out.writeTypedObject(cutout.mBounds, flags);
- if (cutout.mFrameSize != null) {
- out.writeInt(cutout.mFrameSize.getWidth());
- out.writeInt(cutout.mFrameSize.getHeight());
- } else {
- out.writeInt(-1);
- }
}
}
@@ -520,10 +519,7 @@ public final class DisplayCutout {
Rect safeInsets = in.readTypedObject(Rect.CREATOR);
Region bounds = in.readTypedObject(Region.CREATOR);
- int width = in.readInt();
- Size frameSize = width >= 0 ? new Size(width, in.readInt()) : null;
-
- return new DisplayCutout(safeInsets, bounds, frameSize);
+ return new DisplayCutout(safeInsets, bounds, false /* copyArguments */);
}
public DisplayCutout get() {