summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Adrian Roos <roosa@google.com> 2018-03-05 19:50:16 +0100
committer Adrian Roos <roosa@google.com> 2018-03-12 17:26:01 +0000
commit6a4fa0ec183e20c32e7816f5475e72fa9126356c (patch)
treeceedfd2e6e9fd274bb87d5a05472e8ba01f6a875
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
-rw-r--r--api/current.txt6
-rw-r--r--core/java/android/view/DisplayCutout.java240
-rw-r--r--core/java/android/view/WindowManager.java28
-rw-r--r--core/res/res/values/attrs.xml15
-rw-r--r--core/tests/coretests/res/values/styles.xml4
-rw-r--r--core/tests/coretests/src/android/view/DisplayCutoutTest.java114
-rw-r--r--core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java11
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java3
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java3
-rw-r--r--services/core/java/com/android/server/policy/WindowManagerPolicy.java5
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java33
-rw-r--r--services/core/java/com/android/server/wm/DisplayFrames.java19
-rw-r--r--services/core/java/com/android/server/wm/DockedStackDividerController.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java6
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java13
-rw-r--r--services/core/java/com/android/server/wm/utils/WmDisplayCutout.java173
-rw-r--r--services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java20
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java17
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java55
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java157
24 files changed, 677 insertions, 326 deletions
diff --git a/api/current.txt b/api/current.txt
index c2f1bc58bf61..050afb62148f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -46618,8 +46618,8 @@ package android.view {
}
public final class DisplayCutout {
- ctor public DisplayCutout(android.graphics.Rect, android.graphics.Region);
- method public android.graphics.Region getBounds();
+ ctor public DisplayCutout(android.graphics.Rect, java.util.List<android.graphics.Rect>);
+ method public java.util.List<android.graphics.Rect> getBoundingRects();
method public int getSafeInsetBottom();
method public int getSafeInsetLeft();
method public int getSafeInsetRight();
@@ -49693,9 +49693,9 @@ package android.view {
field public static final int LAST_SUB_WINDOW = 1999; // 0x7cf
field public static final int LAST_SYSTEM_WINDOW = 2999; // 0xbb7
field public static final int LAYOUT_CHANGED = 1; // 0x1
- field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 1; // 0x1
field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT = 0; // 0x0
field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER = 2; // 0x2
+ field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES = 1; // 0x1
field public static final int MEMORY_TYPE_CHANGED = 256; // 0x100
field public static final deprecated int MEMORY_TYPE_GPU = 2; // 0x2
field public static final deprecated int MEMORY_TYPE_HARDWARE = 1; // 0x1
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() {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 2354f255c30b..69938cbe8bb6 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2241,18 +2241,20 @@ public interface WindowManager extends ViewManager {
/**
* The window is allowed to extend into the {@link DisplayCutout} area, only if the
- * {@link DisplayCutout} is fully contained within the status bar. Otherwise, the window is
+ * {@link DisplayCutout} is fully contained within a system bar. Otherwise, the window is
* laid out such that it does not overlap with the {@link DisplayCutout} area.
*
* <p>
* In practice, this means that if the window did not set FLAG_FULLSCREEN or
- * SYSTEM_UI_FLAG_FULLSCREEN, it can extend into the cutout area in portrait.
- * Otherwise (i.e. fullscreen or landscape) it is laid out such that it does overlap the
+ * SYSTEM_UI_FLAG_FULLSCREEN, it can extend into the cutout area in portrait if the cutout
+ * is at the top edge. Similarly for SYSTEM_UI_FLAG_HIDE_NAVIGATION and a cutout at the
+ * bottom of the screen.
+ * Otherwise (i.e. fullscreen or landscape) it is laid out such that it does not overlap the
* cutout area.
*
* <p>
- * The usual precautions for not overlapping with the status bar are sufficient for ensuring
- * that no important content overlaps with the DisplayCutout.
+ * The usual precautions for not overlapping with the status and navigation bar are
+ * sufficient for ensuring that no important content overlaps with the DisplayCutout.
*
* @see DisplayCutout
* @see WindowInsets
@@ -2260,8 +2262,18 @@ public interface WindowManager extends ViewManager {
public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT = 0;
/**
- * The window is always allowed to extend into the {@link DisplayCutout} area,
- * even if fullscreen or in landscape.
+ * @deprecated use {@link #LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES}
+ * @hide
+ */
+ @Deprecated
+ public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 1;
+
+ /**
+ * The window is always allowed to extend into the {@link DisplayCutout} areas on the short
+ * edges of the screen.
+ *
+ * The window will never extend into a {@link DisplayCutout} area on the long edges of the
+ * screen.
*
* <p>
* The window must make sure that no important content overlaps with the
@@ -2270,7 +2282,7 @@ public interface WindowManager extends ViewManager {
* @see DisplayCutout
* @see WindowInsets#getDisplayCutout()
*/
- public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 1;
+ public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES = 1;
/**
* The window is never allowed to overlap with the DisplayCutout area.
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 021e88ea6093..b81a74d89755 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2125,29 +2125,32 @@
Defaults to {@code default}.
@see android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
- @see android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ @see android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
@see android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
@see android.view.DisplayCutout
@see android.R.attr#layoutInDisplayCutoutMode -->
<attr name="windowLayoutInDisplayCutoutMode">
<!-- The window is allowed to extend into the {@code DisplayCutout} area, only if the
- {@code DisplayCutout} is fully contained within the status bar. Otherwise, the window is
+ {@code DisplayCutout} is fully contained within a system bar. Otherwise, the window is
laid out such that it does not overlap with the {@code DisplayCutout} area.
@see android.view.DisplayCutout
@see android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
-->
<enum name="default" value="0" />
- <!-- The window is always allowed to extend into the {@code DisplayCutout} area,
- even if fullscreen or in landscape.
+ <!--
+ The window is always allowed to extend into the {@code DisplayCutout} areas on the short
+ edges of the screen even if fullscreen or in landscape.
+ The window will never extend into a {@link DisplayCutout} area on the long edges of the
+ screen.
<p>
The window must make sure that no important content overlaps with the
{@link DisplayCutout}.
@see android.view.DisplayCutout
- @see android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ @see android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
-->
- <enum name="always" value="1" />
+ <enum name="shortEdges" value="1" />
<!-- The window is never allowed to overlap with the DisplayCutout area.
<p>
This should be used with windows that transiently set {@code SYSTEM_UI_FLAG_FULLSCREEN}
diff --git a/core/tests/coretests/res/values/styles.xml b/core/tests/coretests/res/values/styles.xml
index ef3a481ae26c..bcde4c1de137 100644
--- a/core/tests/coretests/res/values/styles.xml
+++ b/core/tests/coretests/res/values/styles.xml
@@ -25,8 +25,8 @@
<style name="LayoutInDisplayCutoutModeDefault">
<item name="android:windowLayoutInDisplayCutoutMode">default</item>
</style>
- <style name="LayoutInDisplayCutoutModeAlways">
- <item name="android:windowLayoutInDisplayCutoutMode">always</item>
+ <style name="LayoutInDisplayCutoutModeShortEdges">
+ <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
<style name="LayoutInDisplayCutoutModeNever">
<item name="android:windowLayoutInDisplayCutoutMode">never</item>
diff --git a/core/tests/coretests/src/android/view/DisplayCutoutTest.java b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
index d80735326568..6e9401d7c7a0 100644
--- a/core/tests/coretests/src/android/view/DisplayCutoutTest.java
+++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
@@ -17,7 +17,6 @@
package android.view;
import static android.view.DisplayCutout.NO_CUTOUT;
-import static android.view.DisplayCutout.fromBoundingRect;
import static android.view.DisplayCutout.fromSpec;
import static org.hamcrest.Matchers.not;
@@ -29,17 +28,17 @@ import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import android.graphics.Rect;
-import android.graphics.Region;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import android.util.Size;
import android.view.DisplayCutout.ParcelableWrapper;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Arrays;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
@Presubmit
@@ -48,7 +47,7 @@ public class DisplayCutoutTest {
/** This is not a consistent cutout. Useful for verifying insets in one go though. */
final DisplayCutout mCutoutNumbers = new DisplayCutout(
new Rect(1, 2, 3, 4),
- new Region(5, 6, 7, 8), new Size(9, 10));
+ Arrays.asList(new Rect(5, 6, 7, 8)));
final DisplayCutout mCutoutTop = createCutoutTop();
@@ -70,7 +69,7 @@ public class DisplayCutoutTest {
@Test
public void getBoundingRect() throws Exception {
- assertEquals(new Rect(50, 0, 75, 100), mCutoutTop.getBoundingRect());
+ assertEquals(new Rect(50, 0, 75, 100), mCutoutTop.getBounds().getBounds());
}
@Test
@@ -154,91 +153,7 @@ public class DisplayCutoutTest {
public void inset_bounds() throws Exception {
DisplayCutout cutout = mCutoutTop.inset(1, 2, 3, 4);
- assertEquals(new Rect(49, -2, 74, 98), cutout.getBoundingRect());
- }
-
- @Test
- public void computeSafeInsets_top() throws Exception {
- DisplayCutout cutout = fromBoundingRect(0, 0, 100, 20)
- .computeSafeInsets(200, 400);
-
- assertEquals(new Rect(0, 20, 0, 0), cutout.getSafeInsets());
- }
-
- @Test
- public void computeSafeInsets_left() throws Exception {
- DisplayCutout cutout = fromBoundingRect(0, 0, 20, 100)
- .computeSafeInsets(400, 200);
-
- assertEquals(new Rect(20, 0, 0, 0), cutout.getSafeInsets());
- }
-
- @Test
- public void computeSafeInsets_bottom() throws Exception {
- DisplayCutout cutout = fromBoundingRect(0, 180, 100, 200)
- .computeSafeInsets(100, 200);
-
- assertEquals(new Rect(0, 0, 0, 20), cutout.getSafeInsets());
- }
-
- @Test
- public void computeSafeInsets_right() throws Exception {
- DisplayCutout cutout = fromBoundingRect(180, 0, 200, 100)
- .computeSafeInsets(200, 100);
-
- assertEquals(new Rect(0, 0, 20, 0), cutout.getSafeInsets());
- }
-
- @Test
- public void computeSafeInsets_bounds() throws Exception {
- DisplayCutout cutout = mCutoutTop.computeSafeInsets(1000, 2000);
-
- assertEquals(mCutoutTop.getBoundingRect(), cutout.getBounds().getBounds());
- }
-
- @Test
- public void calculateRelativeTo_top() throws Exception {
- DisplayCutout cutout = fromBoundingRect(0, 0, 100, 20)
- .computeSafeInsets(200, 400)
- .calculateRelativeTo(new Rect(5, 5, 95, 195));
-
- assertEquals(new Rect(0, 15, 0, 0), cutout.getSafeInsets());
- }
-
- @Test
- public void calculateRelativeTo_left() throws Exception {
- DisplayCutout cutout = fromBoundingRect(0, 0, 20, 100)
- .computeSafeInsets(400, 200)
- .calculateRelativeTo(new Rect(5, 5, 195, 95));
-
- assertEquals(new Rect(15, 0, 0, 0), cutout.getSafeInsets());
- }
-
- @Test
- public void calculateRelativeTo_bottom() throws Exception {
- DisplayCutout cutout = fromBoundingRect(0, 180, 100, 200)
- .computeSafeInsets(100, 200)
- .calculateRelativeTo(new Rect(5, 5, 95, 195));
-
- assertEquals(new Rect(0, 0, 0, 15), cutout.getSafeInsets());
- }
-
- @Test
- public void calculateRelativeTo_right() throws Exception {
- DisplayCutout cutout = fromBoundingRect(180, 0, 200, 100)
- .computeSafeInsets(200, 100)
- .calculateRelativeTo(new Rect(5, 5, 195, 95));
-
- assertEquals(new Rect(0, 0, 15, 0), cutout.getSafeInsets());
- }
-
- @Test
- public void calculateRelativeTo_bounds() throws Exception {
- DisplayCutout cutout = fromBoundingRect(0, 0, 100, 20)
- .computeSafeInsets(200, 400)
- .calculateRelativeTo(new Rect(5, 10, 95, 180));
-
- assertEquals(new Rect(-5, -10, 95, 10), cutout.getBounds().getBounds());
+ assertEquals(new Rect(49, -2, 74, 98), cutout.getBounds().getBounds());
}
@Test
@@ -276,26 +191,26 @@ public class DisplayCutoutTest {
@Test
public void fromSpec_caches() {
- DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 1f);
- assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 1f), sameInstance(cached));
+ DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f);
+ assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f), sameInstance(cached));
}
@Test
public void fromSpec_wontCacheIfSpecChanges() {
- DisplayCutout cached = fromSpec("L1,0 L1000,1000 L0,1 z", 200, 1f);
- assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 1f), not(sameInstance(cached)));
+ DisplayCutout cached = fromSpec("L1,0 L1000,1000 L0,1 z", 200, 400, 1f);
+ assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f), not(sameInstance(cached)));
}
@Test
public void fromSpec_wontCacheIfScreenWidthChanges() {
- DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 2000, 1f);
- assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 1f), not(sameInstance(cached)));
+ DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 2000, 400, 1f);
+ assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f), not(sameInstance(cached)));
}
@Test
public void fromSpec_wontCacheIfDensityChanges() {
- DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 2f);
- assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 1f), not(sameInstance(cached)));
+ DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 2f);
+ assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f), not(sameInstance(cached)));
}
@Test
@@ -348,7 +263,6 @@ public class DisplayCutoutTest {
private static DisplayCutout createCutoutWithInsets(int left, int top, int right, int bottom) {
return new DisplayCutout(
new Rect(left, top, right, bottom),
- new Region(50, 0, 75, 100),
- null);
+ Arrays.asList(new Rect(50, 0, 75, 100)));
}
}
diff --git a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
index c8994ddc457f..002df881f216 100644
--- a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
+++ b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
@@ -19,6 +19,7 @@ package com.android.internal.policy;
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_NEVER;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
@@ -74,12 +75,12 @@ public final class PhoneWindowTest {
}
@Test
- public void layoutInDisplayCutoutMode_always() throws Exception {
- createPhoneWindowWithTheme(R.style.LayoutInDisplayCutoutModeAlways);
+ public void layoutInDisplayCutoutMode_shortEdges() throws Exception {
+ createPhoneWindowWithTheme(R.style.LayoutInDisplayCutoutModeShortEdges);
installDecor();
assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
- is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS));
+ is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES));
}
@Test
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 903f3aa69299..ec2390fbcfc6 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -31,6 +31,7 @@ import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.graphics.Region;
import android.hardware.display.DisplayManager;
import android.provider.Settings.Secure;
import android.support.annotation.VisibleForTesting;
@@ -312,6 +313,7 @@ public class ScreenDecorations extends SystemUI implements Tunable {
private final DisplayInfo mInfo = new DisplayInfo();
private final Paint mPaint = new Paint();
+ private final Region mBounds = new Region();
private final Rect mBoundingRect = new Rect();
private final Path mBoundingPath = new Path();
private final int[] mLocation = new int[2];
@@ -370,11 +372,13 @@ public class ScreenDecorations extends SystemUI implements Tunable {
private void update() {
requestLayout();
getDisplay().getDisplayInfo(mInfo);
+ mBounds.setEmpty();
mBoundingRect.setEmpty();
mBoundingPath.reset();
int newVisible;
if (hasCutout()) {
- mBoundingRect.set(mInfo.displayCutout.getBoundingRect());
+ mBounds.set(mInfo.displayCutout.getBounds());
+ localBounds(mBoundingRect);
mInfo.displayCutout.getBounds().getBoundaryPath(mBoundingPath);
newVisible = VISIBLE;
} else {
@@ -402,7 +406,7 @@ public class ScreenDecorations extends SystemUI implements Tunable {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (mBoundingRect.isEmpty()) {
+ if (mBounds.isEmpty()) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
@@ -410,5 +414,50 @@ public class ScreenDecorations extends SystemUI implements Tunable {
resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0));
}
+
+ public static void boundsFromDirection(DisplayCutout displayCutout, int gravity, Rect out) {
+ Region bounds = displayCutout.getBounds();
+ switch (gravity) {
+ case Gravity.TOP:
+ bounds.op(0, 0, Integer.MAX_VALUE, displayCutout.getSafeInsetTop(),
+ Region.Op.INTERSECT);
+ out.set(bounds.getBounds());
+ break;
+ case Gravity.LEFT:
+ bounds.op(0, 0, displayCutout.getSafeInsetLeft(), Integer.MAX_VALUE,
+ Region.Op.INTERSECT);
+ out.set(bounds.getBounds());
+ break;
+ case Gravity.BOTTOM:
+ bounds.op(0, displayCutout.getSafeInsetTop() + 1, Integer.MAX_VALUE,
+ Integer.MAX_VALUE, Region.Op.INTERSECT);
+ out.set(bounds.getBounds());
+ break;
+ case Gravity.RIGHT:
+ bounds.op(displayCutout.getSafeInsetLeft() + 1, 0, Integer.MAX_VALUE,
+ Integer.MAX_VALUE, Region.Op.INTERSECT);
+ out.set(bounds.getBounds());
+ break;
+ }
+ bounds.recycle();
+ }
+
+ private void localBounds(Rect out) {
+ final DisplayCutout displayCutout = mInfo.displayCutout;
+
+ if (mStart) {
+ if (displayCutout.getSafeInsetLeft() > 0) {
+ boundsFromDirection(displayCutout, Gravity.LEFT, out);
+ } else if (displayCutout.getSafeInsetTop() > 0) {
+ boundsFromDirection(displayCutout, Gravity.TOP, out);
+ }
+ } else {
+ if (displayCutout.getSafeInsetRight() > 0) {
+ boundsFromDirection(displayCutout, Gravity.RIGHT, out);
+ } else if (displayCutout.getSafeInsetBottom() > 0) {
+ boundsFromDirection(displayCutout, Gravity.BOTTOM, out);
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index c5269ad3d147..04c5f2fae986 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
+
import android.annotation.ColorInt;
import android.content.Context;
import android.content.res.Configuration;
@@ -27,6 +29,7 @@ import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.DisplayCutout;
+import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
@@ -42,6 +45,7 @@ import com.android.systemui.BatteryMeterView;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.ScreenDecorations;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
@@ -252,10 +256,13 @@ public class KeyguardStatusBarView extends RelativeLayout
updateLayoutParamsNoCutout();
}
+ Rect bounds = new Rect();
+ boundsFromDirection(dc, Gravity.TOP, bounds);
+
mCutoutSpace.setVisibility(View.VISIBLE);
RelativeLayout.LayoutParams lp = (LayoutParams) mCutoutSpace.getLayoutParams();
- lp.width = dc.getBoundingRect().width();
- lp.height = dc.getBoundingRect().height();
+ lp.width = bounds.width();
+ lp.height = bounds.height();
lp.addRule(RelativeLayout.CENTER_IN_PARENT);
lp = (LayoutParams) mCarrierLabel.getLayoutParams();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 900ec0be4b54..0933c99a1628 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -18,6 +18,8 @@ package com.android.systemui.statusbar.phone;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
+
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
@@ -25,6 +27,7 @@ import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.EventLog;
import android.view.DisplayCutout;
+import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -301,8 +304,12 @@ public class PhoneStatusBarView extends PanelBar {
mCutoutSpace.setVisibility(View.VISIBLE);
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mCutoutSpace.getLayoutParams();
- lp.width = mDisplayCutout.getBoundingRect().width();
- lp.height = mDisplayCutout.getBoundingRect().height();
+
+ Rect bounds = new Rect();
+ boundsFromDirection(mDisplayCutout, Gravity.TOP, bounds);
+
+ lp.width = bounds.width();
+ lp.height = bounds.height();
}
private void updateSafeInsets() {
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index b7385d85d296..0d8ec6d23089 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -404,7 +404,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
&& SystemProperties.getBoolean(PROPERTY_EMULATOR_CIRCULAR, false))) {
mInfo.flags |= DisplayDeviceInfo.FLAG_ROUND;
}
- mInfo.displayCutout = DisplayCutout.fromResources(res, mInfo.width);
+ mInfo.displayCutout = DisplayCutout.fromResources(res, mInfo.width,
+ mInfo.height);
mInfo.type = Display.TYPE_BUILT_IN;
mInfo.densityDpi = (int)(phys.density * 160 + 0.5f);
mInfo.xDpi = phys.xDpi;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index a0e4f4f28568..7efba30bdb48 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4481,7 +4481,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
displayWidth, displayHeight);
outFrame.intersect(taskBounds);
}
- outDisplayCutout.set(displayFrames.mDisplayCutout.calculateRelativeTo(outFrame));
+ outDisplayCutout.set(displayFrames.mDisplayCutout.calculateRelativeTo(outFrame)
+ .getDisplayCutout());
return mForceShowSystemBars;
} else {
if (layoutInScreen) {
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index cd6ff30efd13..a562e34d2c2c 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -93,6 +93,7 @@ import android.view.animation.Animation;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.policy.IShortcutService;
import com.android.server.wm.DisplayFrames;
+import com.android.server.wm.utils.WmDisplayCutout;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -217,11 +218,11 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
* @param stableFrame The frame around which stable system decoration is positioned.
* @param outsetFrame The frame that includes areas that aren't part of the surface but we
* want to treat them as such.
- * @param displayCutout the display displayCutout
+ * @param displayCutout the display cutout
*/
public void computeFrameLw(Rect parentFrame, Rect displayFrame,
Rect overlayFrame, Rect contentFrame, Rect visibleFrame, Rect decorFrame,
- Rect stableFrame, @Nullable Rect outsetFrame, DisplayCutout displayCutout);
+ Rect stableFrame, @Nullable Rect outsetFrame, WmDisplayCutout displayCutout);
/**
* Retrieve the current frame of the window that has been assigned by
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 7b5e8b8139b4..2dce9133d094 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -154,6 +154,7 @@ import com.android.internal.util.ToBooleanFunction;
import com.android.internal.view.IInputMethodClient;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.utils.RotationCache;
+import com.android.server.wm.utils.WmDisplayCutout;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -214,7 +215,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
int mInitialDisplayDensity = 0;
DisplayCutout mInitialDisplayCutout;
- private final RotationCache<DisplayCutout, DisplayCutout> mDisplayCutoutCache
+ private final RotationCache<DisplayCutout, WmDisplayCutout> mDisplayCutoutCache
= new RotationCache<>(this::calculateDisplayCutoutForRotationUncached);
/**
@@ -735,7 +736,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
display.getDisplayInfo(mDisplayInfo);
display.getMetrics(mDisplayMetrics);
isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
- mDisplayFrames = new DisplayFrames(mDisplayId, mDisplayInfo);
+ mDisplayFrames = new DisplayFrames(mDisplayId, mDisplayInfo,
+ calculateDisplayCutoutForRotation(mDisplayInfo.rotation));
initializeDisplayBaseInfo();
mDividerControllerLocked = new DockedStackDividerController(service, this);
mPinnedStackControllerLocked = new PinnedStackController(service, this);
@@ -1128,7 +1130,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
mService.mPolicy.setInitialDisplaySize(getDisplay(),
mBaseDisplayWidth, mBaseDisplayHeight, mBaseDisplayDensity);
- mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo);
+ mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo,
+ calculateDisplayCutoutForRotation(mDisplayInfo.rotation));
}
/**
@@ -1161,8 +1164,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
// Update application display metrics.
- final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(
- mRotation);
+ final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(mRotation);
+ final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout();
+
final int appWidth = mService.mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation, uiMode,
mDisplayId, displayCutout);
final int appHeight = mService.mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation, uiMode,
@@ -1177,7 +1181,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
mDisplayInfo.getLogicalMetrics(mRealDisplayMetrics,
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
}
- mDisplayInfo.displayCutout = displayCutout;
+ mDisplayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout;
mDisplayInfo.getAppMetrics(mDisplayMetrics);
if (mDisplayScalingDisabled) {
mDisplayInfo.flags |= Display.FLAG_SCALING_DISABLED;
@@ -1199,24 +1203,25 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
return mDisplayInfo;
}
- DisplayCutout calculateDisplayCutoutForRotation(int rotation) {
+ WmDisplayCutout calculateDisplayCutoutForRotation(int rotation) {
return mDisplayCutoutCache.getOrCompute(mInitialDisplayCutout, rotation);
}
- private DisplayCutout calculateDisplayCutoutForRotationUncached(
+ private WmDisplayCutout calculateDisplayCutoutForRotationUncached(
DisplayCutout cutout, int rotation) {
if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
- return cutout;
+ return WmDisplayCutout.NO_CUTOUT;
}
if (rotation == ROTATION_0) {
- return cutout.computeSafeInsets(mInitialDisplayWidth, mInitialDisplayHeight);
+ return WmDisplayCutout.computeSafeInsets(
+ cutout, mInitialDisplayWidth, mInitialDisplayHeight);
}
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
final Path bounds = cutout.getBounds().getBoundaryPath();
transformPhysicalToLogicalCoordinates(rotation, mInitialDisplayWidth, mInitialDisplayHeight,
mTmpMatrix);
bounds.transform(mTmpMatrix);
- return DisplayCutout.fromBounds(bounds).computeSafeInsets(
+ return WmDisplayCutout.computeSafeInsets(DisplayCutout.fromBounds(bounds),
rotated ? mInitialDisplayHeight : mInitialDisplayWidth,
rotated ? mInitialDisplayWidth : mInitialDisplayHeight);
}
@@ -1441,7 +1446,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int displayId, int rotation,
int uiMode, int dw, int dh) {
- final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(rotation);
+ final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(
+ rotation).getDisplayCutout();
final int width = mService.mPolicy.getConfigDisplayWidth(dw, dh, rotation, uiMode,
displayId, displayCutout);
if (width < displayInfo.smallestNominalAppWidth) {
@@ -2910,7 +2916,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
Slog.v(TAG, "performLayout: needed=" + isLayoutNeeded() + " dw=" + dw + " dh=" + dh);
}
- mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo);
+ mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo,
+ calculateDisplayCutoutForRotation(mDisplayInfo.rotation));
// TODO: Not sure if we really need to set the rotation here since we are updating from the
// display info above...
mDisplayFrames.mRotation = mRotation;
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index 57ce15bc82f5..57693ac2b463 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -27,6 +27,8 @@ import android.util.proto.ProtoOutputStream;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
+import com.android.server.wm.utils.WmDisplayCutout;
+
import java.io.PrintWriter;
/**
@@ -97,10 +99,10 @@ public class DisplayFrames {
public final Rect mDock = new Rect();
/** The display cutout used for layout (after rotation) */
- @NonNull public DisplayCutout mDisplayCutout = DisplayCutout.NO_CUTOUT;
+ @NonNull public WmDisplayCutout mDisplayCutout = WmDisplayCutout.NO_CUTOUT;
/** The cutout as supplied by display info */
- @NonNull private DisplayCutout mDisplayInfoCutout = DisplayCutout.NO_CUTOUT;
+ @NonNull public WmDisplayCutout mDisplayInfoCutout = WmDisplayCutout.NO_CUTOUT;
/**
* During layout, the frame that is display-cutout safe, i.e. that does not intersect with it.
@@ -114,19 +116,18 @@ public class DisplayFrames {
public int mRotation;
- public DisplayFrames(int displayId, DisplayInfo info) {
+ public DisplayFrames(int displayId, DisplayInfo info, WmDisplayCutout displayCutout) {
mDisplayId = displayId;
- onDisplayInfoUpdated(info);
+ onDisplayInfoUpdated(info, displayCutout);
}
- public void onDisplayInfoUpdated(DisplayInfo info) {
+ public void onDisplayInfoUpdated(DisplayInfo info, WmDisplayCutout displayCutout) {
mDisplayWidth = info.logicalWidth;
mDisplayHeight = info.logicalHeight;
mRotation = info.rotation;
mDisplayInfoOverscan.set(
info.overscanLeft, info.overscanTop, info.overscanRight, info.overscanBottom);
- mDisplayInfoCutout = info.displayCutout != null
- ? info.displayCutout : DisplayCutout.NO_CUTOUT;
+ mDisplayInfoCutout = displayCutout != null ? displayCutout : WmDisplayCutout.NO_CUTOUT;
}
public void onBeginLayout() {
@@ -171,8 +172,8 @@ public class DisplayFrames {
mDisplayCutout = mDisplayInfoCutout;
mDisplayCutoutSafe.set(Integer.MIN_VALUE, Integer.MIN_VALUE,
Integer.MAX_VALUE, Integer.MAX_VALUE);
- if (!mDisplayCutout.isEmpty()) {
- final DisplayCutout c = mDisplayCutout;
+ if (!mDisplayCutout.getDisplayCutout().isEmpty()) {
+ final DisplayCutout c = mDisplayCutout.getDisplayCutout();
if (c.getSafeInsetLeft() > 0) {
mDisplayCutoutSafe.left = mRestrictedOverscan.left + c.getSafeInsetLeft();
}
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 1f1efc4ed77a..b99e85fed46b 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -175,7 +175,7 @@ public class DockedStackDividerController {
getContentWidth());
final DisplayCutout displayCutout = mDisplayContent.calculateDisplayCutoutForRotation(
- rotation);
+ rotation).getDisplayCutout();
// Since we only care about feasible states, snap to the closest snap target, like it
// would happen when actually rotating the screen.
@@ -233,7 +233,7 @@ public class DockedStackDividerController {
? mDisplayContent.mBaseDisplayWidth
: mDisplayContent.mBaseDisplayHeight;
final DisplayCutout displayCutout =
- mDisplayContent.calculateDisplayCutoutForRotation(rotation);
+ mDisplayContent.calculateDisplayCutoutForRotation(rotation).getDisplayCutout();
mService.mPolicy.getStableInsetsLw(rotation, dw, dh, displayCutout, mTmpRect);
config.unset();
config.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 6b5ad6013086..80dab2281940 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1437,7 +1437,9 @@ public class WindowManagerService extends IWindowManager.Stub
final DisplayFrames displayFrames = displayContent.mDisplayFrames;
// TODO: Not sure if onDisplayInfoUpdated() call is needed.
- displayFrames.onDisplayInfoUpdated(displayContent.getDisplayInfo());
+ final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+ displayFrames.onDisplayInfoUpdated(displayInfo,
+ displayContent.calculateDisplayCutoutForRotation(displayInfo.rotation));
final Rect taskBounds;
if (atoken != null && atoken.getTask() != null) {
taskBounds = mTmpRect;
@@ -2096,7 +2098,7 @@ public class WindowManagerService extends IWindowManager.Stub
win.mLastRelayoutContentInsets.set(win.mContentInsets);
outVisibleInsets.set(win.mVisibleInsets);
outStableInsets.set(win.mStableInsets);
- outCutout.set(win.mDisplayCutout);
+ outCutout.set(win.mDisplayCutout.getDisplayCutout());
outOutsets.set(win.mOutsets);
outBackdropFrame.set(win.getBackdropFrame(win.mFrame));
if (localLOGV) Slog.v(
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index c4185fa9a599..145aee9b0488 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -202,6 +202,7 @@ import com.android.internal.util.ToBooleanFunction;
import com.android.server.input.InputWindowHandle;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
+import com.android.server.wm.utils.WmDisplayCutout;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -351,8 +352,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
private boolean mOutsetsChanged = false;
/** Part of the display that has been cut away. See {@link DisplayCutout}. */
- DisplayCutout mDisplayCutout = DisplayCutout.NO_CUTOUT;
- private DisplayCutout mLastDisplayCutout = DisplayCutout.NO_CUTOUT;
+ WmDisplayCutout mDisplayCutout = WmDisplayCutout.NO_CUTOUT;
+ private WmDisplayCutout mLastDisplayCutout = WmDisplayCutout.NO_CUTOUT;
private boolean mDisplayCutoutChanged;
/**
@@ -833,7 +834,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
@Override
public void computeFrameLw(Rect parentFrame, Rect displayFrame, Rect overscanFrame,
Rect contentFrame, Rect visibleFrame, Rect decorFrame, Rect stableFrame,
- Rect outsetFrame, DisplayCutout displayCutout) {
+ Rect outsetFrame, WmDisplayCutout displayCutout) {
if (mWillReplaceWindow && (mAnimatingExit || !mReplacingRemoveRequested)) {
// This window is being replaced and either already got information that it's being
// removed or we are still waiting for some information. Because of this we don't
@@ -2914,7 +2915,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
final boolean reportDraw = mWinAnimator.mDrawState == DRAW_PENDING;
final boolean reportOrientation = mReportOrientationChanged;
final int displayId = getDisplayId();
- final DisplayCutout displayCutout = mDisplayCutout;
+ final DisplayCutout displayCutout = mDisplayCutout.getDisplayCutout();
if (mAttrs.type != WindowManager.LayoutParams.TYPE_APPLICATION_STARTING
&& mClient instanceof IWindow.Stub) {
// To prevent deadlock simulate one-way call if win.mClient is a local object.
@@ -3186,7 +3187,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mVisibleInsets.writeToProto(proto, VISIBLE_INSETS);
mStableInsets.writeToProto(proto, STABLE_INSETS);
mOutsets.writeToProto(proto, OUTSETS);
- mDisplayCutout.writeToProto(proto, CUTOUT);
+ mDisplayCutout.getDisplayCutout().writeToProto(proto, CUTOUT);
proto.write(REMOVE_ON_EXIT, mRemoveOnExit);
proto.write(DESTROYING, mDestroying);
proto.write(REMOVED, mRemoved);
@@ -3332,7 +3333,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
pw.print(" stable="); mStableInsets.printShortString(pw);
pw.print(" surface="); mAttrs.surfaceInsets.printShortString(pw);
pw.print(" outsets="); mOutsets.printShortString(pw);
- pw.print(" cutout=" + mDisplayCutout);
+ pw.print(" cutout=" + mDisplayCutout.getDisplayCutout());
pw.println();
pw.print(prefix); pw.print("Lst insets: overscan=");
mLastOverscanInsets.printShortString(pw);
diff --git a/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java b/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
new file mode 100644
index 000000000000..ea3f758fb209
--- /dev/null
+++ b/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2018 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 com.android.server.wm.utils;
+
+import android.graphics.Rect;
+import android.util.Size;
+import android.view.DisplayCutout;
+import android.view.Gravity;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Wrapper for DisplayCutout that also tracks the display size and using this allows (re)calculating
+ * safe insets.
+ */
+public class WmDisplayCutout {
+
+ public static final WmDisplayCutout NO_CUTOUT = new WmDisplayCutout(DisplayCutout.NO_CUTOUT,
+ null);
+
+ private final DisplayCutout mInner;
+ private final Size mFrameSize;
+
+ public WmDisplayCutout(DisplayCutout inner, Size frameSize) {
+ mInner = inner;
+ mFrameSize = frameSize;
+ }
+
+ public static WmDisplayCutout computeSafeInsets(DisplayCutout inner,
+ int displayWidth, int displayHeight) {
+ if (inner == DisplayCutout.NO_CUTOUT || inner.isBoundsEmpty()) {
+ return NO_CUTOUT;
+ }
+
+ final Size displaySize = new Size(displayWidth, displayHeight);
+ final Rect safeInsets = computeSafeInsets(displaySize, inner);
+ return new WmDisplayCutout(inner.replaceSafeInsets(safeInsets), displaySize);
+ }
+
+ /**
+ * Insets the reference frame of the cutout in the given directions.
+ *
+ * @return a copy of this instance which has been inset
+ * @hide
+ */
+ public WmDisplayCutout inset(int insetLeft, int insetTop, int insetRight, int insetBottom) {
+ DisplayCutout newInner = mInner.inset(insetLeft, insetTop, insetRight, insetBottom);
+
+ if (mInner == newInner) {
+ return this;
+ }
+
+ Size frame = mFrameSize == null ? null : new Size(
+ mFrameSize.getWidth() - insetLeft - insetRight,
+ mFrameSize.getHeight() - insetTop - insetBottom);
+
+ return new WmDisplayCutout(newInner, frame);
+ }
+
+ /**
+ * Recalculates the cutout relative to the given reference frame.
+ *
+ * The safe insets must already have been computed, e.g. with {@link #computeSafeInsets}.
+ *
+ * @return a copy of this instance with the safe insets recalculated
+ * @hide
+ */
+ public WmDisplayCutout calculateRelativeTo(Rect frame) {
+ if (mInner.isEmpty()) {
+ return this;
+ }
+ return inset(frame.left, frame.top,
+ mFrameSize.getWidth() - frame.right, mFrameSize.getHeight() - frame.bottom);
+ }
+
+ /**
+ * Calculates the safe insets relative to the given display size.
+ *
+ * @return a copy of this instance with the safe insets calculated
+ * @hide
+ */
+ public WmDisplayCutout computeSafeInsets(int width, int height) {
+ return computeSafeInsets(mInner, width, height);
+ }
+
+ private static Rect computeSafeInsets(Size displaySize, DisplayCutout cutout) {
+ if (displaySize.getWidth() < displaySize.getHeight()) {
+ final List<Rect> boundingRects = cutout.replaceSafeInsets(
+ new Rect(0, displaySize.getHeight() / 2, 0, displaySize.getHeight() / 2))
+ .getBoundingRects();
+ int topInset = findInsetForSide(displaySize, boundingRects, Gravity.TOP);
+ int bottomInset = findInsetForSide(displaySize, boundingRects, Gravity.BOTTOM);
+ return new Rect(0, topInset, 0, bottomInset);
+ } else if (displaySize.getWidth() > displaySize.getHeight()) {
+ final List<Rect> boundingRects = cutout.replaceSafeInsets(
+ new Rect(displaySize.getWidth() / 2, 0, displaySize.getWidth() / 2, 0))
+ .getBoundingRects();
+ int leftInset = findInsetForSide(displaySize, boundingRects, Gravity.LEFT);
+ int right = findInsetForSide(displaySize, boundingRects, Gravity.RIGHT);
+ return new Rect(leftInset, 0, right, 0);
+ } else {
+ throw new UnsupportedOperationException("not implemented: display=" + displaySize +
+ " cutout=" + cutout);
+ }
+ }
+
+ private static int findInsetForSide(Size display, List<Rect> boundingRects, int gravity) {
+ int inset = 0;
+ final int size = boundingRects.size();
+ for (int i = 0; i < size; i++) {
+ Rect boundingRect = boundingRects.get(i);
+ switch (gravity) {
+ case Gravity.TOP:
+ if (boundingRect.top == 0) {
+ inset = Math.max(inset, boundingRect.bottom);
+ }
+ break;
+ case Gravity.BOTTOM:
+ if (boundingRect.bottom == display.getHeight()) {
+ inset = Math.max(inset, display.getHeight() - boundingRect.top);
+ }
+ break;
+ case Gravity.LEFT:
+ if (boundingRect.left == 0) {
+ inset = Math.max(inset, boundingRect.right);
+ }
+ break;
+ case Gravity.RIGHT:
+ if (boundingRect.right == display.getWidth()) {
+ inset = Math.max(inset, display.getWidth() - boundingRect.left);
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("unknown gravity: " + gravity);
+ }
+ }
+ return inset;
+ }
+
+ public DisplayCutout getDisplayCutout() {
+ return mInner;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof WmDisplayCutout)) {
+ return false;
+ }
+ WmDisplayCutout that = (WmDisplayCutout) o;
+ return Objects.equals(mInner, that.mInner) &&
+ Objects.equals(mFrameSize, that.mFrameSize);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mInner, mFrameSize);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java b/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java
index 5de393c7ae2b..f3539fed7aec 100644
--- a/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java
+++ b/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java
@@ -25,6 +25,8 @@ import android.view.DisplayCutout;
import android.view.IApplicationToken;
import android.view.WindowManager;
+import com.android.server.wm.utils.WmDisplayCutout;
+
public class FakeWindowState implements WindowManagerPolicy.WindowState {
public final Rect parentFrame = new Rect();
@@ -36,7 +38,7 @@ public class FakeWindowState implements WindowManagerPolicy.WindowState {
public final Rect stableFrame = new Rect();
public Rect outsetFrame = new Rect();
- public DisplayCutout displayCutout;
+ public WmDisplayCutout displayCutout;
public WindowManager.LayoutParams attrs;
public int displayId;
@@ -61,7 +63,7 @@ public class FakeWindowState implements WindowManagerPolicy.WindowState {
@Override
public void computeFrameLw(Rect parentFrame, Rect displayFrame, Rect overlayFrame,
Rect contentFrame, Rect visibleFrame, Rect decorFrame, Rect stableFrame,
- @Nullable Rect outsetFrame, DisplayCutout displayCutout) {
+ @Nullable Rect outsetFrame, WmDisplayCutout displayCutout) {
this.parentFrame.set(parentFrame);
this.displayFrame.set(displayFrame);
this.overscanFrame.set(overlayFrame);
diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java
index 1d4348c0b6d4..195dd39758df 100644
--- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java
@@ -41,6 +41,7 @@ import android.os.IBinder;
import android.os.UserHandle;
import android.support.test.InstrumentationRegistry;
import android.testing.TestableResources;
+import android.util.Pair;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
@@ -53,6 +54,7 @@ import android.view.accessibility.IAccessibilityManager;
import com.android.server.policy.keyguard.KeyguardServiceDelegate;
import com.android.server.wm.DisplayFrames;
+import com.android.server.wm.utils.WmDisplayCutout;
import org.junit.Before;
@@ -98,8 +100,9 @@ public class PhoneWindowManagerTestBase {
}
private void updateDisplayFrames() {
- DisplayInfo info = displayInfoForRotation(mRotation, mHasDisplayCutout);
- mFrames = new DisplayFrames(Display.DEFAULT_DISPLAY, info);
+ Pair<DisplayInfo, WmDisplayCutout> info = displayInfoAndCutoutForRotation(mRotation,
+ mHasDisplayCutout);
+ mFrames = new DisplayFrames(Display.DEFAULT_DISPLAY, info.first, info.second);
}
public void addStatusBar() {
@@ -146,19 +149,26 @@ public class PhoneWindowManagerTestBase {
}
public static DisplayInfo displayInfoForRotation(int rotation, boolean withDisplayCutout) {
+ return displayInfoAndCutoutForRotation(rotation, withDisplayCutout).first;
+ }
+ public static Pair<DisplayInfo, WmDisplayCutout> displayInfoAndCutoutForRotation(int rotation,
+ boolean withDisplayCutout) {
DisplayInfo info = new DisplayInfo();
+ WmDisplayCutout cutout = null;
final boolean flippedDimensions = rotation == ROTATION_90 || rotation == ROTATION_270;
info.logicalWidth = flippedDimensions ? DISPLAY_HEIGHT : DISPLAY_WIDTH;
info.logicalHeight = flippedDimensions ? DISPLAY_WIDTH : DISPLAY_HEIGHT;
info.rotation = rotation;
if (withDisplayCutout) {
- info.displayCutout = displayCutoutForRotation(rotation)
- .computeSafeInsets(info.logicalWidth, info.logicalHeight);
+ cutout = WmDisplayCutout.computeSafeInsets(
+ displayCutoutForRotation(rotation), info.logicalWidth,
+ info.logicalHeight);
+ info.displayCutout = cutout.getDisplayCutout();
} else {
info.displayCutout = null;
}
- return info;
+ return Pair.create(info, cutout);
}
private static DisplayCutout displayCutoutForRotation(int rotation) {
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index 064e077ee9ed..ccde049b99ca 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -50,6 +50,8 @@ import android.view.DisplayCutout;
import android.view.MotionEvent;
import android.view.Surface;
+import com.android.server.wm.utils.WmDisplayCutout;
+
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
@@ -398,8 +400,9 @@ public class DisplayContentTests extends WindowTestsBase {
dc.mInitialDisplayWidth = 200;
dc.mInitialDisplayHeight = 400;
Rect r = new Rect(80, 0, 120, 10);
- final DisplayCutout cutout = fromBoundingRect(r.left, r.top, r.right, r.bottom)
- .computeSafeInsets(200, 400);
+ final DisplayCutout cutout = new WmDisplayCutout(
+ fromBoundingRect(r.left, r.top, r.right, r.bottom), null)
+ .computeSafeInsets(200, 400).getDisplayCutout();
dc.mInitialDisplayCutout = cutout;
dc.setRotation(Surface.ROTATION_0);
@@ -416,16 +419,18 @@ public class DisplayContentTests extends WindowTestsBase {
dc.mInitialDisplayWidth = 200;
dc.mInitialDisplayHeight = 400;
Rect r1 = new Rect(80, 0, 120, 10);
- final DisplayCutout cutout = fromBoundingRect(r1.left, r1.top, r1.right, r1.bottom)
- .computeSafeInsets(200, 400);
+ final DisplayCutout cutout = new WmDisplayCutout(
+ fromBoundingRect(r1.left, r1.top, r1.right, r1.bottom), null)
+ .computeSafeInsets(200, 400).getDisplayCutout();
dc.mInitialDisplayCutout = cutout;
dc.setRotation(Surface.ROTATION_90);
dc.computeScreenConfiguration(new Configuration()); // recomputes dc.mDisplayInfo.
final Rect r = new Rect(0, 80, 10, 120);
- assertEquals(fromBoundingRect(r.left, r.top, r.right, r.bottom)
- .computeSafeInsets(400, 200), dc.getDisplayInfo().displayCutout);
+ assertEquals(new WmDisplayCutout(
+ fromBoundingRect(r.left, r.top, r.right, r.bottom), null)
+ .computeSafeInsets(400, 200).getDisplayCutout(), dc.getDisplayInfo().displayCutout);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
index b5d5fc607732..bd212a909088 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
@@ -26,7 +26,6 @@ import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.IWindow;
@@ -38,6 +37,8 @@ import static android.view.WindowManager.LayoutParams.FILL_PARENT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import com.android.server.wm.utils.WmDisplayCutout;
+
/**
* Tests for the {@link WindowState#computeFrameLw} method and other window frame machinery.
*
@@ -159,7 +160,7 @@ public class WindowFrameTests extends WindowTestsBase {
// When mFrame extends past cf, the content insets are
// the difference between mFrame and ContentFrame. Visible
// and stable frames work the same way.
- w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, DisplayCutout.NO_CUTOUT);
+ w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, WmDisplayCutout.NO_CUTOUT);
assertRect(w.mFrame,0, 0, 1000, 1000);
assertRect(w.mContentInsets, 0, topContentInset, 0, bottomContentInset);
assertRect(w.mVisibleInsets, 0, topVisibleInset, 0, bottomVisibleInset);
@@ -174,7 +175,7 @@ public class WindowFrameTests extends WindowTestsBase {
w.mAttrs.width = 100; w.mAttrs.height = 100; //have to clear MATCH_PARENT
w.mRequestedWidth = 100;
w.mRequestedHeight = 100;
- w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, DisplayCutout.NO_CUTOUT);
+ w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, WmDisplayCutout.NO_CUTOUT);
assertRect(w.mFrame, 100, 100, 200, 200);
assertRect(w.mContentInsets, 0, 0, 0, 0);
// In this case the frames are shrunk to the window frame.
@@ -195,7 +196,7 @@ public class WindowFrameTests extends WindowTestsBase {
// Here the window has FILL_PARENT, FILL_PARENT
// so we expect it to fill the entire available frame.
- w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, DisplayCutout.NO_CUTOUT);
+ w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT);
assertRect(w.mFrame, 0, 0, 1000, 1000);
// It can select various widths and heights within the bounds.
@@ -203,14 +204,14 @@ public class WindowFrameTests extends WindowTestsBase {
// and we use mRequestedWidth/mRequestedHeight
w.mAttrs.width = 300;
w.mAttrs.height = 300;
- w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, DisplayCutout.NO_CUTOUT);
+ w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT);
// Explicit width and height without requested width/height
// gets us nothing.
assertRect(w.mFrame, 0, 0, 0, 0);
w.mRequestedWidth = 300;
w.mRequestedHeight = 300;
- w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, DisplayCutout.NO_CUTOUT);
+ w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT);
// With requestedWidth/Height we can freely choose our size within the
// parent bounds.
assertRect(w.mFrame, 0, 0, 300, 300);
@@ -223,14 +224,14 @@ public class WindowFrameTests extends WindowTestsBase {
w.mRequestedWidth = -1;
w.mAttrs.width = 100;
w.mAttrs.height = 100;
- w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, DisplayCutout.NO_CUTOUT);
+ w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT);
assertRect(w.mFrame, 0, 0, 100, 100);
w.mAttrs.flags = 0;
// But sizes too large will be clipped to the containing frame
w.mRequestedWidth = 1200;
w.mRequestedHeight = 1200;
- w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, DisplayCutout.NO_CUTOUT);
+ w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT);
assertRect(w.mFrame, 0, 0, 1000, 1000);
// Before they are clipped though windows will be shifted
@@ -238,7 +239,7 @@ public class WindowFrameTests extends WindowTestsBase {
w.mAttrs.y = 300;
w.mRequestedWidth = 1000;
w.mRequestedHeight = 1000;
- w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, DisplayCutout.NO_CUTOUT);
+ w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT);
assertRect(w.mFrame, 0, 0, 1000, 1000);
// If there is room to move around in the parent frame the window will be shifted according
@@ -248,16 +249,16 @@ public class WindowFrameTests extends WindowTestsBase {
w.mRequestedWidth = 300;
w.mRequestedHeight = 300;
w.mAttrs.gravity = Gravity.RIGHT | Gravity.TOP;
- w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, DisplayCutout.NO_CUTOUT);
+ w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT);
assertRect(w.mFrame, 700, 0, 1000, 300);
w.mAttrs.gravity = Gravity.RIGHT | Gravity.BOTTOM;
- w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, DisplayCutout.NO_CUTOUT);
+ w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT);
assertRect(w.mFrame, 700, 700, 1000, 1000);
// Window specified x and y are interpreted as offsets in the opposite
// direction of gravity
w.mAttrs.x = 100;
w.mAttrs.y = 100;
- w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, DisplayCutout.NO_CUTOUT);
+ w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT);
assertRect(w.mFrame, 600, 600, 900, 900);
}
@@ -278,7 +279,7 @@ public class WindowFrameTests extends WindowTestsBase {
w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight);
- w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, null, DisplayCutout.NO_CUTOUT);
+ w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, null, WmDisplayCutout.NO_CUTOUT);
// For non fullscreen tasks the containing frame is based off the
// task bounds not the parent frame.
assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom);
@@ -290,7 +291,7 @@ public class WindowFrameTests extends WindowTestsBase {
final int cfRight = logicalWidth / 2;
final int cfBottom = logicalHeight / 2;
final Rect cf = new Rect(0, 0, cfRight, cfBottom);
- w.computeFrameLw(pf, pf, pf, cf, cf, pf, cf, null, DisplayCutout.NO_CUTOUT);
+ w.computeFrameLw(pf, pf, pf, cf, cf, pf, cf, null, WmDisplayCutout.NO_CUTOUT);
assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom);
int contentInsetRight = taskRight - cfRight;
int contentInsetBottom = taskBottom - cfBottom;
@@ -307,7 +308,7 @@ public class WindowFrameTests extends WindowTestsBase {
final int insetRight = insetLeft + (taskRight - taskLeft);
final int insetBottom = insetTop + (taskBottom - taskTop);
task.mInsetBounds.set(insetLeft, insetTop, insetRight, insetBottom);
- w.computeFrameLw(pf, pf, pf, cf, cf, pf, cf, null, DisplayCutout.NO_CUTOUT);
+ w.computeFrameLw(pf, pf, pf, cf, cf, pf, cf, null, WmDisplayCutout.NO_CUTOUT);
assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom);
contentInsetRight = insetRight - cfRight;
contentInsetBottom = insetBottom - cfBottom;
@@ -339,13 +340,13 @@ public class WindowFrameTests extends WindowTestsBase {
final Rect policyCrop = new Rect();
- w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, DisplayCutout.NO_CUTOUT);
+ w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, WmDisplayCutout.NO_CUTOUT);
w.calculatePolicyCrop(policyCrop);
assertRect(policyCrop, 0, cf.top, logicalWidth, cf.bottom);
dcf.setEmpty();
// Likewise with no decor frame we would get no crop
- w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, DisplayCutout.NO_CUTOUT);
+ w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, WmDisplayCutout.NO_CUTOUT);
w.calculatePolicyCrop(policyCrop);
assertRect(policyCrop, 0, 0, logicalWidth, logicalHeight);
@@ -358,7 +359,7 @@ public class WindowFrameTests extends WindowTestsBase {
w.mAttrs.height = logicalHeight / 2;
w.mRequestedWidth = logicalWidth / 2;
w.mRequestedHeight = logicalHeight / 2;
- w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, DisplayCutout.NO_CUTOUT);
+ w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT);
w.calculatePolicyCrop(policyCrop);
// Normally the crop is shrunk from the decor frame
@@ -395,7 +396,7 @@ public class WindowFrameTests extends WindowTestsBase {
final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight);
w.computeFrameLw(pf /* parentFrame */, pf /* displayFrame */, pf /* overscanFrame */,
pf /* contentFrame */, pf /* visibleFrame */, pf /* decorFrame */,
- pf /* stableFrame */, null /* outsetFrame */, DisplayCutout.NO_CUTOUT);
+ pf /* stableFrame */, null /* outsetFrame */, WmDisplayCutout.NO_CUTOUT);
// For non fullscreen tasks the containing frame is based off the
// task bounds not the parent frame.
assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom);
@@ -414,7 +415,7 @@ public class WindowFrameTests extends WindowTestsBase {
w.computeFrameLw(pf /* parentFrame */, pf /* displayFrame */, pf /* overscanFrame */,
cf /* contentFrame */, cf /* visibleFrame */, pf /* decorFrame */,
- cf /* stableFrame */, null /* outsetFrame */, DisplayCutout.NO_CUTOUT);
+ cf /* stableFrame */, null /* outsetFrame */, WmDisplayCutout.NO_CUTOUT);
assertEquals(cf, w.mFrame);
assertEquals(cf, w.getContentFrameLw());
assertRect(w.mContentInsets, 0, 0, 0, 0);
@@ -427,17 +428,17 @@ public class WindowFrameTests extends WindowTestsBase {
WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT);
w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
- final Rect pf = new Rect(0, 0, 1000, 1000);
+ final Rect pf = new Rect(0, 0, 1000, 2000);
// Create a display cutout of size 50x50, aligned top-center
- final DisplayCutout cutout = fromBoundingRect(500, 0, 550, 50)
- .computeSafeInsets(pf.width(), pf.height());
+ final WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ fromBoundingRect(500, 0, 550, 50), pf.width(), pf.height());
w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, cutout);
- assertEquals(w.mDisplayCutout.getSafeInsetTop(), 50);
- assertEquals(w.mDisplayCutout.getSafeInsetBottom(), 0);
- assertEquals(w.mDisplayCutout.getSafeInsetLeft(), 0);
- assertEquals(w.mDisplayCutout.getSafeInsetRight(), 0);
+ assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetTop(), 50);
+ assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetBottom(), 0);
+ assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetLeft(), 0);
+ assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetRight(), 0);
}
private WindowStateWithTask createWindow(Task task, int width, int height) {
diff --git a/services/tests/servicestests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java b/services/tests/servicestests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java
new file mode 100644
index 000000000000..f7addf6c77f9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2018 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 com.android.server.wm.utils;
+
+
+import static android.view.DisplayCutout.NO_CUTOUT;
+import static android.view.DisplayCutout.fromBoundingRect;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Size;
+import android.view.DisplayCutout;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+/**
+ * Tests for {@link WmDisplayCutout}
+ *
+ * Run with: atest WmDisplayCutoutTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class WmDisplayCutoutTest {
+
+ private final DisplayCutout mCutoutTop = new DisplayCutout(
+ new Rect(0, 100, 0, 0),
+ Arrays.asList(new Rect(50, 0, 75, 100)));
+
+ @Test
+ public void calculateRelativeTo_top() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ fromBoundingRect(0, 0, 100, 20), 200, 400)
+ .calculateRelativeTo(new Rect(5, 5, 95, 195));
+
+ assertEquals(new Rect(0, 15, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void calculateRelativeTo_left() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ fromBoundingRect(0, 0, 20, 100), 400, 200)
+ .calculateRelativeTo(new Rect(5, 5, 195, 95));
+
+ assertEquals(new Rect(15, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void calculateRelativeTo_bottom() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ fromBoundingRect(0, 180, 100, 200), 100, 200)
+ .calculateRelativeTo(new Rect(5, 5, 95, 195));
+
+ assertEquals(new Rect(0, 0, 0, 15), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void calculateRelativeTo_right() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ fromBoundingRect(180, 0, 200, 100), 200, 100)
+ .calculateRelativeTo(new Rect(5, 5, 195, 95));
+
+ assertEquals(new Rect(0, 0, 15, 0), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void calculateRelativeTo_bounds() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ fromBoundingRect(0, 0, 100, 20), 200, 400)
+ .calculateRelativeTo(new Rect(5, 10, 95, 180));
+
+ assertEquals(new Rect(-5, -10, 95, 10), cutout.getDisplayCutout().getBounds().getBounds());
+ }
+
+ @Test
+ public void computeSafeInsets_top() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ fromBoundingRect(0, 0, 100, 20), 200, 400);
+
+ assertEquals(new Rect(0, 20, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void computeSafeInsets_left() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ fromBoundingRect(0, 0, 20, 100), 400, 200);
+
+ assertEquals(new Rect(20, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void computeSafeInsets_bottom() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ fromBoundingRect(0, 180, 100, 200), 100, 200);
+
+ assertEquals(new Rect(0, 0, 0, 20), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void computeSafeInsets_right() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ fromBoundingRect(180, 0, 200, 100), 200, 100);
+
+ assertEquals(new Rect(0, 0, 20, 0), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void computeSafeInsets_bounds() {
+ DisplayCutout cutout = WmDisplayCutout.computeSafeInsets(mCutoutTop, 1000,
+ 2000).getDisplayCutout();
+
+ assertEquals(mCutoutTop.getBounds().getBounds(),
+ cutout.getBounds().getBounds());
+ }
+
+ @Test
+ public void test_equals() {
+ assertEquals(new WmDisplayCutout(NO_CUTOUT, null), new WmDisplayCutout(NO_CUTOUT, null));
+ assertEquals(new WmDisplayCutout(mCutoutTop, new Size(1, 2)),
+ new WmDisplayCutout(mCutoutTop, new Size(1, 2)));
+
+ assertNotEquals(new WmDisplayCutout(mCutoutTop, new Size(1, 2)),
+ new WmDisplayCutout(mCutoutTop, new Size(5, 6)));
+ assertNotEquals(new WmDisplayCutout(mCutoutTop, new Size(1, 2)),
+ new WmDisplayCutout(NO_CUTOUT, new Size(1, 2)));
+ }
+
+ @Test
+ public void test_hashCode() {
+ assertEquals(new WmDisplayCutout(NO_CUTOUT, null).hashCode(),
+ new WmDisplayCutout(NO_CUTOUT, null).hashCode());
+ assertEquals(new WmDisplayCutout(mCutoutTop, new Size(1, 2)).hashCode(),
+ new WmDisplayCutout(mCutoutTop, new Size(1, 2)).hashCode());
+ }
+} \ No newline at end of file