diff --git a/packages/SystemUI/res/layout/rounded_corners_bottom.xml b/packages/SystemUI/res/layout/rounded_corners_bottom.xml
index b2857abc19d2c..bb6d4bddf25ad 100644
--- a/packages/SystemUI/res/layout/rounded_corners_bottom.xml
+++ b/packages/SystemUI/res/layout/rounded_corners_bottom.xml
@@ -25,6 +25,7 @@
android:layout_height="12dp"
android:layout_gravity="left|bottom"
android:tint="#ff000000"
+ android:visibility="gone"
android:src="@drawable/rounded_corner_bottom"/>
diff --git a/packages/SystemUI/res/layout/rounded_corners_top.xml b/packages/SystemUI/res/layout/rounded_corners_top.xml
index 9937c215e71cd..46648c88d921e 100644
--- a/packages/SystemUI/res/layout/rounded_corners_top.xml
+++ b/packages/SystemUI/res/layout/rounded_corners_top.xml
@@ -25,6 +25,7 @@
android:layout_height="12dp"
android:layout_gravity="left|top"
android:tint="#ff000000"
+ android:visibility="gone"
android:src="@drawable/rounded_corner_top"/>
diff --git a/packages/SystemUI/res/layout/screen_decor_hwc_layer.xml b/packages/SystemUI/res/layout/screen_decor_hwc_layer.xml
new file mode 100644
index 0000000000000..1c17e7512491d
--- /dev/null
+++ b/packages/SystemUI/res/layout/screen_decor_hwc_layer.xml
@@ -0,0 +1,21 @@
+
+
+
diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
new file mode 100644
index 0000000000000..6bec8aacd55fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2022 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.systemui
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.annotation.Dimension
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Matrix
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.RectF
+import android.graphics.Region
+import android.util.AttributeSet
+import android.view.Display
+import android.view.DisplayCutout
+import android.view.DisplayInfo
+import android.view.Surface
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView
+import com.android.systemui.animation.Interpolators
+
+/**
+ * A class that handles common actions of display cutout view.
+ * - Draws cutouts.
+ * - Handles camera protection.
+ * - Intercepts touches on cutout areas.
+ */
+open class DisplayCutoutBaseView : View, RegionInterceptableView {
+
+ private val shouldDrawCutout: Boolean = DisplayCutout.getFillBuiltInDisplayCutout(
+ context.resources, context.display?.uniqueId)
+ private var displayMode: Display.Mode? = null
+ private val location = IntArray(2)
+ protected var displayRotation = 0
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ @JvmField val displayInfo = DisplayInfo()
+ @JvmField protected var pendingRotationChange = false
+ @JvmField protected val paint = Paint()
+ @JvmField protected val cutoutPath = Path()
+
+ @JvmField protected var showProtection = false
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ @JvmField val protectionRect: RectF = RectF()
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ @JvmField val protectionPath: Path = Path()
+ private val protectionRectOrig: RectF = RectF()
+ private val protectionPathOrig: Path = Path()
+ private var cameraProtectionProgress: Float = HIDDEN_CAMERA_PROTECTION_SCALE
+ private var cameraProtectionAnimator: ValueAnimator? = null
+
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+
+ constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
+ : super(context, attrs, defStyleAttr)
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ updateCutout()
+ }
+
+ fun onDisplayChanged(displayId: Int) {
+ val oldMode: Display.Mode? = displayMode
+ displayMode = display.mode
+
+ // Skip if display mode or cutout hasn't changed.
+ if (!displayModeChanged(oldMode, displayMode) &&
+ display.cutout == displayInfo.displayCutout) {
+ return
+ }
+ if (displayId == display.displayId) {
+ updateCutout()
+ updateProtectionBoundingPath()
+ }
+ }
+
+ open fun updateRotation(rotation: Int) {
+ displayRotation = rotation
+ updateCutout()
+ updateProtectionBoundingPath()
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ public override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+ if (!shouldDrawCutout) {
+ return
+ }
+ canvas.save()
+ getLocationOnScreen(location)
+ canvas.translate(-location[0].toFloat(), -location[1].toFloat())
+
+ drawCutouts(canvas)
+ drawCutoutProtection(canvas)
+ canvas.restore()
+ }
+
+ override fun shouldInterceptTouch(): Boolean {
+ return displayInfo.displayCutout != null && visibility == VISIBLE && shouldDrawCutout
+ }
+
+ override fun getInterceptRegion(): Region? {
+ displayInfo.displayCutout ?: return null
+
+ val cutoutBounds: Region = rectsToRegion(displayInfo.displayCutout?.boundingRects)
+ // Transform to window's coordinate space
+ rootView.getLocationOnScreen(location)
+ cutoutBounds.translate(-location[0], -location[1])
+
+ // Intersect with window's frame
+ cutoutBounds.op(rootView.left, rootView.top, rootView.right, rootView.bottom,
+ Region.Op.INTERSECT)
+ return cutoutBounds
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ open fun updateCutout() {
+ if (pendingRotationChange) {
+ return
+ }
+ cutoutPath.reset()
+ display.getDisplayInfo(displayInfo)
+ displayInfo.displayCutout?.cutoutPath?.let { path -> cutoutPath.set(path) }
+ invalidate()
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ open fun drawCutouts(canvas: Canvas) {
+ displayInfo.displayCutout?.cutoutPath ?: return
+ canvas.drawPath(cutoutPath, paint)
+ }
+
+ protected open fun drawCutoutProtection(canvas: Canvas) {
+ if (cameraProtectionProgress > HIDDEN_CAMERA_PROTECTION_SCALE &&
+ !protectionRect.isEmpty) {
+ canvas.scale(cameraProtectionProgress, cameraProtectionProgress,
+ protectionRect.centerX(), protectionRect.centerY())
+ canvas.drawPath(protectionPath, paint)
+ }
+ }
+
+ /**
+ * Converts a set of [Rect]s into a [Region]
+ */
+ fun rectsToRegion(rects: List?): Region {
+ val result = Region.obtain()
+ if (rects != null) {
+ for (r in rects) {
+ if (r != null && !r.isEmpty) {
+ result.op(r, Region.Op.UNION)
+ }
+ }
+ }
+ return result
+ }
+
+ open fun enableShowProtection(show: Boolean) {
+ if (showProtection == show) {
+ return
+ }
+ showProtection = show
+ updateProtectionBoundingPath()
+ // Delay the relayout until the end of the animation when hiding the cutout,
+ // otherwise we'd clip it.
+ if (showProtection) {
+ requestLayout()
+ }
+ cameraProtectionAnimator?.cancel()
+ cameraProtectionAnimator = ValueAnimator.ofFloat(cameraProtectionProgress,
+ if (showProtection) 1.0f else HIDDEN_CAMERA_PROTECTION_SCALE).setDuration(750)
+ cameraProtectionAnimator?.interpolator = Interpolators.DECELERATE_QUINT
+ cameraProtectionAnimator?.addUpdateListener(ValueAnimator.AnimatorUpdateListener {
+ animation: ValueAnimator ->
+ cameraProtectionProgress = animation.animatedValue as Float
+ invalidate()
+ })
+ cameraProtectionAnimator?.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ cameraProtectionAnimator = null
+ if (!showProtection) {
+ requestLayout()
+ }
+ }
+ })
+ cameraProtectionAnimator?.start()
+ }
+
+ open fun setProtection(path: Path, pathBounds: Rect) {
+ protectionPathOrig.reset()
+ protectionPathOrig.set(path)
+ protectionPath.reset()
+ protectionRectOrig.setEmpty()
+ protectionRectOrig.set(pathBounds)
+ protectionRect.setEmpty()
+ }
+
+ protected open fun updateProtectionBoundingPath() {
+ if (pendingRotationChange) {
+ return
+ }
+ val lw: Int = displayInfo.logicalWidth
+ val lh: Int = displayInfo.logicalHeight
+ val flipped = (displayInfo.rotation == Surface.ROTATION_90 ||
+ displayInfo.rotation == Surface.ROTATION_270)
+ val dw = if (flipped) lh else lw
+ val dh = if (flipped) lw else lh
+ val m = Matrix()
+ transformPhysicalToLogicalCoordinates(displayInfo.rotation, dw, dh, m)
+ if (!protectionPathOrig.isEmpty) {
+ // Reset the protection path so we don't aggregate rotations
+ protectionPath.set(protectionPathOrig)
+ protectionPath.transform(m)
+ m.mapRect(protectionRect, protectionRectOrig)
+ }
+ }
+
+ private fun displayModeChanged(oldMode: Display.Mode?, newMode: Display.Mode?): Boolean {
+ if (oldMode == null) {
+ return true
+ }
+
+ // We purposely ignore refresh rate and id changes here, because we don't need to
+ // invalidate for those, and they can trigger the refresh rate to increase
+ return oldMode?.physicalHeight != newMode?.physicalHeight ||
+ oldMode?.physicalWidth != newMode?.physicalWidth
+ }
+
+ companion object {
+ private const val HIDDEN_CAMERA_PROTECTION_SCALE = 0.5f
+
+ @JvmStatic protected fun transformPhysicalToLogicalCoordinates(
+ @Surface.Rotation rotation: Int,
+ @Dimension physicalWidth: Int,
+ @Dimension physicalHeight: Int,
+ out: Matrix
+ ) {
+ when (rotation) {
+ Surface.ROTATION_0 -> out.reset()
+ Surface.ROTATION_90 -> {
+ out.setRotate(270f)
+ out.postTranslate(0f, physicalWidth.toFloat())
+ }
+ Surface.ROTATION_180 -> {
+ out.setRotate(180f)
+ out.postTranslate(physicalWidth.toFloat(), physicalHeight.toFloat())
+ }
+ Surface.ROTATION_270 -> {
+ out.setRotate(90f)
+ out.postTranslate(physicalHeight.toFloat(), 0f)
+ }
+ else -> throw IllegalArgumentException("Unknown rotation: $rotation")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
new file mode 100644
index 0000000000000..ee1d9a3aa5de4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2022 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.systemui
+
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorFilter
+import android.graphics.Paint
+import android.graphics.PixelFormat
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.graphics.PorterDuffXfermode
+import android.graphics.drawable.Drawable
+import android.hardware.graphics.common.AlphaInterpretation
+import android.hardware.graphics.common.DisplayDecorationSupport
+import android.view.RoundedCorner
+import android.view.RoundedCorners
+
+/**
+ * When the HWC of the device supports Composition.DISPLAY_DECORATON, we use this layer to draw
+ * screen decorations.
+ */
+class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDecorationSupport)
+ : DisplayCutoutBaseView(context) {
+ public val colorMode: Int
+ private val useInvertedAlphaColor: Boolean
+ private val color: Int
+ private val bgColor: Int
+ private val cornerFilter: ColorFilter
+ private val cornerBgFilter: ColorFilter
+ private val clearPaint: Paint
+
+ private var roundedCornerTopSize = 0
+ private var roundedCornerBottomSize = 0
+ private var roundedCornerDrawableTop: Drawable? = null
+ private var roundedCornerDrawableBottom: Drawable? = null
+
+ init {
+ if (displayDecorationSupport.format != PixelFormat.R_8) {
+ throw IllegalArgumentException("Attempting to use unsupported mode " +
+ "${PixelFormat.formatToString(displayDecorationSupport.format)}")
+ }
+ if (DEBUG_COLOR) {
+ color = Color.GREEN
+ bgColor = Color.TRANSPARENT
+ colorMode = ActivityInfo.COLOR_MODE_DEFAULT
+ useInvertedAlphaColor = false
+ } else {
+ colorMode = ActivityInfo.COLOR_MODE_A8
+ useInvertedAlphaColor = displayDecorationSupport.alphaInterpretation ==
+ AlphaInterpretation.COVERAGE
+ if (useInvertedAlphaColor) {
+ color = Color.TRANSPARENT
+ bgColor = Color.BLACK
+ } else {
+ color = Color.BLACK
+ bgColor = Color.TRANSPARENT
+ }
+ }
+ cornerFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)
+ cornerBgFilter = PorterDuffColorFilter(bgColor, PorterDuff.Mode.SRC_OUT)
+
+ clearPaint = Paint()
+ clearPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
+ }
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ viewRootImpl.setDisplayDecoration(true)
+
+ if (useInvertedAlphaColor) {
+ paint.set(clearPaint)
+ } else {
+ paint.color = color
+ paint.style = Paint.Style.FILL
+ }
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ if (useInvertedAlphaColor) {
+ canvas.drawColor(bgColor)
+ }
+ // Cutouts are drawn in DisplayCutoutBaseView.onDraw()
+ super.onDraw(canvas)
+ drawRoundedCorners(canvas)
+ }
+
+ private fun drawRoundedCorners(canvas: Canvas) {
+ if (roundedCornerTopSize == 0 && roundedCornerBottomSize == 0) {
+ return
+ }
+ var degree: Int
+ for (i in RoundedCorner.POSITION_TOP_LEFT
+ until RoundedCorners.ROUNDED_CORNER_POSITION_LENGTH) {
+ canvas.save()
+ degree = getRoundedCornerRotationDegree(90 * i)
+ canvas.rotate(degree.toFloat())
+ canvas.translate(
+ getRoundedCornerTranslationX(degree).toFloat(),
+ getRoundedCornerTranslationY(degree).toFloat())
+ if (i == RoundedCorner.POSITION_TOP_LEFT || i == RoundedCorner.POSITION_TOP_RIGHT) {
+ drawRoundedCorner(canvas, roundedCornerDrawableTop, roundedCornerTopSize)
+ } else {
+ drawRoundedCorner(canvas, roundedCornerDrawableBottom, roundedCornerBottomSize)
+ }
+ canvas.restore()
+ }
+ }
+
+ private fun drawRoundedCorner(canvas: Canvas, drawable: Drawable?, size: Int) {
+ if (useInvertedAlphaColor) {
+ canvas.drawRect(0f, 0f, size.toFloat(), size.toFloat(), clearPaint)
+ drawable?.colorFilter = cornerBgFilter
+ } else {
+ drawable?.colorFilter = cornerFilter
+ }
+ drawable?.draw(canvas)
+ // Clear color filter when we are done with drawing.
+ drawable?.clearColorFilter()
+ }
+
+ private fun getRoundedCornerRotationDegree(defaultDegree: Int): Int {
+ return (defaultDegree - 90 * displayRotation + 360) % 360
+ }
+
+ private fun getRoundedCornerTranslationX(degree: Int): Int {
+ return when (degree) {
+ 0, 90 -> 0
+ 180 -> -width
+ 270 -> -height
+ else -> throw IllegalArgumentException("Incorrect degree: $degree")
+ }
+ }
+
+ private fun getRoundedCornerTranslationY(degree: Int): Int {
+ return when (degree) {
+ 0, 270 -> 0
+ 90 -> -width
+ 180 -> -height
+ else -> throw IllegalArgumentException("Incorrect degree: $degree")
+ }
+ }
+
+ /**
+ * Update the rounded corner drawables.
+ */
+ fun updateRoundedCornerDrawable(top: Drawable, bottom: Drawable) {
+ roundedCornerDrawableTop = top
+ roundedCornerDrawableBottom = bottom
+ updateRoundedCornerDrawableBounds()
+ invalidate()
+ }
+
+ /**
+ * Update the rounded corner size.
+ */
+ fun updateRoundedCornerSize(top: Int, bottom: Int) {
+ roundedCornerTopSize = top
+ roundedCornerBottomSize = bottom
+ updateRoundedCornerDrawableBounds()
+ invalidate()
+ }
+
+ private fun updateRoundedCornerDrawableBounds() {
+ if (roundedCornerDrawableTop != null) {
+ roundedCornerDrawableTop?.setBounds(0, 0, roundedCornerTopSize,
+ roundedCornerTopSize)
+ }
+ if (roundedCornerDrawableBottom != null) {
+ roundedCornerDrawableBottom?.setBounds(0, 0, roundedCornerBottomSize,
+ roundedCornerBottomSize)
+ }
+ invalidate()
+ }
+
+ companion object {
+ private val DEBUG_COLOR = ScreenDecorations.DEBUG_COLOR
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 783415e98875f..2ec5f4f894f93 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -14,24 +14,17 @@
package com.android.systemui;
-import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM;
import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
import static android.view.DisplayCutout.BOUNDS_POSITION_LENGTH;
import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT;
import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
-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 android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.annotation.Dimension;
import android.annotation.IdRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -40,11 +33,11 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
@@ -52,10 +45,10 @@ import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManager;
+import android.hardware.graphics.common.AlphaInterpretation;
+import android.hardware.graphics.common.DisplayDecorationSupport;
import android.os.Handler;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -63,14 +56,11 @@ import android.provider.Settings.Secure;
import android.util.DisplayMetrics;
import android.util.DisplayUtils;
import android.util.Log;
-import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayCutout.BoundsPosition;
-import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.RoundedCorners;
-import android.view.Surface;
import android.view.View;
import android.view.View.OnLayoutChangeListener;
import android.view.ViewGroup;
@@ -83,8 +73,6 @@ import android.widget.ImageView;
import androidx.annotation.VisibleForTesting;
import com.android.internal.util.Preconditions;
-import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
@@ -106,6 +94,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -117,7 +106,7 @@ import kotlin.Pair;
* for antialiasing and emulation purposes.
*/
@SysUISingleton
-public class ScreenDecorations extends CoreStartable implements Tunable , Dumpable{
+public class ScreenDecorations extends CoreStartable implements Tunable , Dumpable {
private static final boolean DEBUG = false;
private static final String TAG = "ScreenDecorations";
@@ -129,7 +118,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS =
SystemProperties.getBoolean("debug.screenshot_rounded_corners", false);
private static final boolean VERBOSE = false;
- private static final boolean DEBUG_COLOR = DEBUG_SCREENSHOT_ROUNDED_CORNERS;
+ static final boolean DEBUG_COLOR = DEBUG_SCREENSHOT_ROUNDED_CORNERS;
private DisplayManager mDisplayManager;
@VisibleForTesting
@@ -138,7 +127,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
private final Executor mMainExecutor;
private final TunerService mTunerService;
private final SecureSettings mSecureSettings;
- private DisplayManager.DisplayListener mDisplayListener;
+ @VisibleForTesting
+ DisplayManager.DisplayListener mDisplayListener;
private CameraAvailabilityListener mCameraListener;
private final UserTracker mUserTracker;
private final PrivacyDotViewController mDotViewController;
@@ -158,23 +148,36 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
protected OverlayWindow[] mOverlays = null;
@Nullable
private DisplayCutoutView[] mCutoutViews;
+ @VisibleForTesting
+ ViewGroup mScreenDecorHwcWindow;
+ @VisibleForTesting
+ ScreenDecorHwcLayer mScreenDecorHwcLayer;
private float mDensity;
private WindowManager mWindowManager;
private int mRotation;
private SettingObserver mColorInversionSetting;
private DelayableExecutor mExecutor;
private Handler mHandler;
- private boolean mPendingRotationChange;
+ boolean mPendingRotationChange;
private boolean mIsRoundedCornerMultipleRadius;
private Drawable mRoundedCornerDrawable;
private Drawable mRoundedCornerDrawableTop;
private Drawable mRoundedCornerDrawableBottom;
- private String mDisplayUniqueId;
+ @VisibleForTesting
+ String mDisplayUniqueId;
+ private int mTintColor = Color.BLACK;
+ @VisibleForTesting
+ protected DisplayDecorationSupport mHwcScreenDecorationSupport;
private CameraAvailabilityListener.CameraTransitionCallback mCameraTransitionCallback =
new CameraAvailabilityListener.CameraTransitionCallback() {
@Override
public void onApplyCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) {
+ if (mScreenDecorHwcLayer != null) {
+ mScreenDecorHwcLayer.setProtection(protectionPath, bounds);
+ mScreenDecorHwcLayer.enableShowProtection(true);
+ return;
+ }
if (mCutoutViews == null) {
Log.w(TAG, "DisplayCutoutView do not initialized");
return;
@@ -184,13 +187,17 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
// Check Null since not all mCutoutViews[pos] be inflated at the meanwhile
if (dcv != null) {
dcv.setProtection(protectionPath, bounds);
- dcv.setShowProtection(true);
+ dcv.enableShowProtection(true);
}
}
}
@Override
public void onHideCameraProtection() {
+ if (mScreenDecorHwcLayer != null) {
+ mScreenDecorHwcLayer.enableShowProtection(false);
+ return;
+ }
if (mCutoutViews == null) {
Log.w(TAG, "DisplayCutoutView do not initialized");
return;
@@ -199,27 +206,59 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
for (DisplayCutoutView dcv : mCutoutViews) {
// Check Null since not all mCutoutViews[pos] be inflated at the meanwhile
if (dcv != null) {
- dcv.setShowProtection(false);
+ dcv.enableShowProtection(false);
}
}
}
};
- /**
- * Converts a set of {@link Rect}s into a {@link Region}
- *
- * @hide
- */
- public static Region rectsToRegion(List rects) {
- Region result = Region.obtain();
- if (rects != null) {
- for (Rect r : rects) {
- if (r != null && !r.isEmpty()) {
- result.op(r, Region.Op.UNION);
- }
+ private PrivacyDotViewController.ShowingListener mPrivacyDotShowingListener =
+ new PrivacyDotViewController.ShowingListener() {
+ @Override
+ public void onPrivacyDotShown(@Nullable View v) {
+ // We don't need to control the window visibility when the hwc doesn't support screen
+ // decoration since the overlay windows are always visible in this case.
+ if (mHwcScreenDecorationSupport == null || v == null) {
+ return;
}
+ mExecutor.execute(() -> {
+ for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
+ if (mOverlays[i] == null) {
+ continue;
+ }
+ final ViewGroup overlayView = mOverlays[i].getRootView();
+ if (overlayView.findViewById(v.getId()) != null) {
+ overlayView.setVisibility(View.VISIBLE);
+ }
+ }
+ });
}
- return result;
+
+ @Override
+ public void onPrivacyDotHidden(@Nullable View v) {
+ // We don't need to control the window visibility when the hwc doesn't support screen
+ // decoration since the overlay windows are always visible in this case.
+ if (mHwcScreenDecorationSupport == null || v == null) {
+ return;
+ }
+ mExecutor.execute(() -> {
+ for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
+ if (mOverlays[i] == null) {
+ continue;
+ }
+ final ViewGroup overlayView = mOverlays[i].getRootView();
+ if (overlayView.findViewById(v.getId()) != null) {
+ overlayView.setVisibility(View.INVISIBLE);
+ }
+ }
+ });
+ }
+ };
+
+ private static boolean eq(DisplayDecorationSupport a, DisplayDecorationSupport b) {
+ if (a == null) return (b == null);
+ if (b == null) return false;
+ return a.format == b.format && a.alphaInterpretation == b.alphaInterpretation;
}
@Inject
@@ -241,6 +280,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
mDotViewController = dotViewController;
mThreadFactory = threadFactory;
mDotFactory = dotFactory;
+ dotViewController.setShowingListener(mPrivacyDotShowingListener);
}
@Override
@@ -265,6 +305,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
mIsRoundedCornerMultipleRadius = isRoundedCornerMultipleRadius(mContext, mDisplayUniqueId);
mWindowManager = mContext.getSystemService(WindowManager.class);
mDisplayManager = mContext.getSystemService(DisplayManager.class);
+ mHwcScreenDecorationSupport = mContext.getDisplay().getDisplayDecorationSupport();
updateRoundedCornerDrawable();
updateRoundedCornerRadii();
setupDecorations();
@@ -305,15 +346,36 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
new RestartingPreDrawListener(overlayView, i, newRotation));
}
}
+
+ if (mScreenDecorHwcWindow != null) {
+ mScreenDecorHwcWindow.getViewTreeObserver().addOnPreDrawListener(
+ new RestartingPreDrawListener(
+ mScreenDecorHwcWindow,
+ -1, // Pass -1 for views with no specific position.
+ newRotation));
+ }
}
+
final String newUniqueId = mContext.getDisplay().getUniqueId();
- if ((newUniqueId != null && !newUniqueId.equals(mDisplayUniqueId))
- || (mDisplayUniqueId != null && !mDisplayUniqueId.equals(newUniqueId))) {
+ if (!Objects.equals(newUniqueId, mDisplayUniqueId)) {
mDisplayUniqueId = newUniqueId;
mIsRoundedCornerMultipleRadius =
isRoundedCornerMultipleRadius(mContext, mDisplayUniqueId);
+ final DisplayDecorationSupport newScreenDecorationSupport =
+ mContext.getDisplay().getDisplayDecorationSupport();
+ // When the value of mSupportHwcScreenDecoration is changed, re-setup the whole
+ // screen decoration.
+ if (!eq(newScreenDecorationSupport, mHwcScreenDecorationSupport)) {
+ mHwcScreenDecorationSupport = newScreenDecorationSupport;
+ removeAllOverlays();
+ setupDecorations();
+ return;
+ }
updateRoundedCornerDrawable();
}
+ if (mScreenDecorHwcLayer != null) {
+ mScreenDecorHwcLayer.onDisplayChanged(displayId);
+ }
updateOrientation();
}
};
@@ -359,6 +421,11 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
List decorProviders = mDotFactory.getProviders();
if (hasRoundedCorners() || shouldDrawCutout() || !decorProviders.isEmpty()) {
+ if (mHwcScreenDecorationSupport != null) {
+ createHwcOverlay();
+ } else {
+ removeHwcOverlay();
+ }
final DisplayCutout cutout = getCutout();
for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
if (shouldShowCutout(i, cutout) || shouldShowRoundedCorner(i, cutout)
@@ -383,14 +450,15 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
}
} else {
removeAllOverlays();
+ removeHwcOverlay();
}
- if (hasOverlays()) {
+ if (hasOverlays() || hasHwcOverlay()) {
if (mIsRegistered) {
return;
}
DisplayMetrics metrics = new DisplayMetrics();
- mDisplayManager.getDisplay(DEFAULT_DISPLAY).getMetrics(metrics);
+ mContext.getDisplay().getMetrics(metrics);
mDensity = metrics.density;
mMainExecutor.execute(() -> mTunerService.addTunable(this, SIZE));
@@ -475,25 +543,27 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
mOverlays = new OverlayWindow[BOUNDS_POSITION_LENGTH];
}
- if (mCutoutViews == null) {
- mCutoutViews = new DisplayCutoutView[BOUNDS_POSITION_LENGTH];
- }
-
if (mOverlays[pos] != null) {
return;
}
mOverlays[pos] = overlayForPosition(pos, decorProviders);
-
- mCutoutViews[pos] = new DisplayCutoutView(mContext, pos, this);
- mOverlays[pos].getRootView().addView(mCutoutViews[pos]);
-
final ViewGroup overlayView = mOverlays[pos].getRootView();
overlayView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
overlayView.setAlpha(0);
overlayView.setForceDarkAllowed(false);
- updateView(pos, cutout);
+ // Only show cutout and rounded corners in mOverlays when hwc don't support screen
+ // decoration.
+ if (mHwcScreenDecorationSupport == null) {
+ if (mCutoutViews == null) {
+ mCutoutViews = new DisplayCutoutView[BOUNDS_POSITION_LENGTH];
+ }
+ mCutoutViews[pos] = new DisplayCutoutView(mContext, pos);
+ mCutoutViews[pos].setColor(mTintColor);
+ overlayView.addView(mCutoutViews[pos]);
+ updateView(pos, cutout);
+ }
mWindowManager.addView(overlayView, getWindowLayoutParams(pos));
@@ -509,8 +579,37 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
}
});
- mOverlays[pos].getRootView().getViewTreeObserver().addOnPreDrawListener(
- new ValidatingPreDrawListener(mOverlays[pos].getRootView()));
+ overlayView.getRootView().getViewTreeObserver().addOnPreDrawListener(
+ new ValidatingPreDrawListener(overlayView.getRootView()));
+ }
+
+ private boolean hasHwcOverlay() {
+ return mScreenDecorHwcWindow != null;
+ }
+
+ private void removeHwcOverlay() {
+ if (mScreenDecorHwcWindow == null) {
+ return;
+ }
+ mWindowManager.removeViewImmediate(mScreenDecorHwcWindow);
+ mScreenDecorHwcWindow = null;
+ mScreenDecorHwcLayer = null;
+ }
+
+ private void createHwcOverlay() {
+ if (mScreenDecorHwcWindow != null) {
+ return;
+ }
+ mScreenDecorHwcWindow = (ViewGroup) LayoutInflater.from(mContext).inflate(
+ R.layout.screen_decor_hwc_layer, null);
+ mScreenDecorHwcLayer = new ScreenDecorHwcLayer(mContext, mHwcScreenDecorationSupport);
+ mScreenDecorHwcWindow.addView(mScreenDecorHwcLayer, new FrameLayout.LayoutParams(
+ MATCH_PARENT, MATCH_PARENT, Gravity.TOP | Gravity.START));
+ mWindowManager.addView(mScreenDecorHwcWindow, getHwcWindowLayoutParams());
+ updateRoundedCornerSize(mRoundedDefault, mRoundedDefaultTop, mRoundedDefaultBottom);
+ updateRoundedCornerImageView();
+ mScreenDecorHwcWindow.getViewTreeObserver().addOnPreDrawListener(
+ new ValidatingPreDrawListener(mScreenDecorHwcWindow));
}
/**
@@ -523,12 +622,18 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
decorProviders.forEach(provider -> {
removeOverlayView(provider.getViewId());
currentOverlay.addDecorProvider(provider, mRotation);
+ // If the hwc supports screen decoration and privacy dot is enabled, it means there will
+ // be only privacy dot in mOverlay. So set the initial visibility of mOverlays to
+ // INVISIBLE and will only set it to VISIBLE when the privacy dot is showing.
+ if (mHwcScreenDecorationSupport != null) {
+ currentOverlay.getRootView().setVisibility(View.INVISIBLE);
+ }
});
return currentOverlay;
}
private void updateView(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
- if (mOverlays == null || mOverlays[pos] == null) {
+ if (mOverlays == null || mOverlays[pos] == null || mHwcScreenDecorationSupport != null) {
return;
}
@@ -540,15 +645,34 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
// update cutout view rotation
if (mCutoutViews != null && mCutoutViews[pos] != null) {
- mCutoutViews[pos].setRotation(mRotation);
+ mCutoutViews[pos].updateRotation(mRotation);
}
}
@VisibleForTesting
WindowManager.LayoutParams getWindowLayoutParams(@BoundsPosition int pos) {
+ final WindowManager.LayoutParams lp = getWindowLayoutBaseParams();
+ lp.width = getWidthLayoutParamByPos(pos);
+ lp.height = getHeightLayoutParamByPos(pos);
+ lp.setTitle(getWindowTitleByPos(pos));
+ lp.gravity = getOverlayWindowGravity(pos);
+ return lp;
+ }
+
+ private WindowManager.LayoutParams getHwcWindowLayoutParams() {
+ final WindowManager.LayoutParams lp = getWindowLayoutBaseParams();
+ lp.width = MATCH_PARENT;
+ lp.height = MATCH_PARENT;
+ lp.setTitle("ScreenDecorHwcOverlay");
+ lp.gravity = Gravity.TOP | Gravity.START;
+ if (!DEBUG_COLOR) {
+ lp.setColorMode(ActivityInfo.COLOR_MODE_A8);
+ }
+ return lp;
+ }
+
+ private WindowManager.LayoutParams getWindowLayoutBaseParams() {
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- getWidthLayoutParamByPos(pos),
- getHeightLayoutParamByPos(pos),
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
@@ -566,8 +690,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
}
- lp.setTitle(getWindowTitleByPos(pos));
- lp.gravity = getOverlayWindowGravity(pos);
lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
lp.setFitInsetsTypes(0 /* types */);
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
@@ -648,15 +770,19 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
};
private void updateColorInversion(int colorsInvertedValue) {
- int tint = colorsInvertedValue != 0 ? Color.WHITE : Color.BLACK;
+ mTintColor = colorsInvertedValue != 0 ? Color.WHITE : Color.BLACK;
if (DEBUG_COLOR) {
- tint = Color.RED;
+ mTintColor = Color.RED;
}
- ColorStateList tintList = ColorStateList.valueOf(tint);
- if (mOverlays == null) {
+ // When the hwc supports screen decorations, the layer will use the A8 color mode which
+ // won't be affected by the color inversion. If the composition goes the client composition
+ // route, the color inversion will be handled by the RenderEngine.
+ if (mOverlays == null || mHwcScreenDecorationSupport != null) {
return;
}
+
+ ColorStateList tintList = ColorStateList.valueOf(mTintColor);
for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
if (mOverlays[i] == null) {
continue;
@@ -676,7 +802,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
if (child instanceof ImageView) {
((ImageView) child).setImageTintList(tintList);
} else if (child instanceof DisplayCutoutView) {
- ((DisplayCutoutView) child).setColor(tint);
+ ((DisplayCutoutView) child).setColor(mTintColor);
}
}
}
@@ -688,6 +814,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
Log.i(TAG, "ScreenDecorations is disabled");
return;
}
+
mExecutor.execute(() -> {
int oldRotation = mRotation;
mPendingRotationChange = false;
@@ -705,6 +832,14 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
});
}
+ private static String alphaInterpretationToString(int alpha) {
+ switch (alpha) {
+ case AlphaInterpretation.COVERAGE: return "COVERAGE";
+ case AlphaInterpretation.MASK: return "MASK";
+ default: return "Unknown: " + alpha;
+ }
+ }
+
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("ScreenDecorations state:");
@@ -712,6 +847,15 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
pw.println(" mIsRoundedCornerMultipleRadius:" + mIsRoundedCornerMultipleRadius);
pw.println(" mIsPrivacyDotEnabled:" + isPrivacyDotEnabled());
pw.println(" mPendingRotationChange:" + mPendingRotationChange);
+ pw.println(" mHwcScreenDecorationSupport:");
+ if (mHwcScreenDecorationSupport == null) {
+ pw.println(" null");
+ } else {
+ pw.println(" format: "
+ + PixelFormat.formatToString(mHwcScreenDecorationSupport.format));
+ pw.println(" alphaInterpretation: "
+ + alphaInterpretationToString(mHwcScreenDecorationSupport.alphaInterpretation));
+ }
pw.println(" mRoundedDefault(x,y)=(" + mRoundedDefault.x + "," + mRoundedDefault.y + ")");
pw.println(" mRoundedDefaultTop(x,y)=(" + mRoundedDefaultTop.x + "," + mRoundedDefaultTop.y
+ ")");
@@ -739,7 +883,10 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
}
if (newRotation != mRotation) {
mRotation = newRotation;
-
+ if (mScreenDecorHwcLayer != null) {
+ mScreenDecorHwcLayer.pendingRotationChange = false;
+ mScreenDecorHwcLayer.updateRotation(mRotation);
+ }
if (mOverlays != null) {
updateLayoutParams();
final DisplayCutout cutout = getCutout();
@@ -956,7 +1103,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
private boolean shouldShowRoundedCorner(@BoundsPosition int pos,
@Nullable DisplayCutout cutout) {
- return hasRoundedCorners() && isDefaultShownOverlayPos(pos, cutout);
+ return hasRoundedCorners() && isDefaultShownOverlayPos(pos, cutout)
+ && mHwcScreenDecorationSupport == null;
}
private boolean shouldShowPrivacyDot(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
@@ -966,7 +1114,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
private boolean shouldShowCutout(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
final Rect[] bounds = cutout == null ? null : cutout.getBoundingRectsAll();
final int rotatedPos = getBoundPositionFromRotation(pos, mRotation);
- return (bounds != null && !bounds[rotatedPos].isEmpty());
+ return (bounds != null && !bounds[rotatedPos].isEmpty()
+ && mHwcScreenDecorationSupport == null);
}
private boolean shouldDrawCutout() {
@@ -1027,14 +1176,22 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
final Drawable bottom = mRoundedCornerDrawableBottom != null
? mRoundedCornerDrawableBottom : mRoundedCornerDrawable;
+ if (mScreenDecorHwcLayer != null) {
+ mScreenDecorHwcLayer.updateRoundedCornerDrawable(top, bottom);
+ return;
+ }
+
if (mOverlays == null) {
return;
}
+ final ColorStateList colorStateList = ColorStateList.valueOf(mTintColor);
for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
if (mOverlays[i] == null) {
continue;
}
final ViewGroup overlayView = mOverlays[i].getRootView();
+ ((ImageView) overlayView.findViewById(R.id.left)).setImageTintList(colorStateList);
+ ((ImageView) overlayView.findViewById(R.id.right)).setImageTintList(colorStateList);
((ImageView) overlayView.findViewById(R.id.left)).setImageDrawable(
isTopRoundedCorner(i, R.id.left) ? top : bottom);
((ImageView) overlayView.findViewById(R.id.right)).setImageDrawable(
@@ -1065,9 +1222,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
Point sizeDefault,
Point sizeTop,
Point sizeBottom) {
- if (mOverlays == null) {
- return;
- }
if (sizeTop.x == 0) {
sizeTop = sizeDefault;
}
@@ -1075,6 +1229,14 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
sizeBottom = sizeDefault;
}
+ if (mScreenDecorHwcLayer != null) {
+ mScreenDecorHwcLayer.updateRoundedCornerSize(sizeTop.x, sizeBottom.x);
+ return;
+ }
+
+ if (mOverlays == null) {
+ return;
+ }
for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
if (mOverlays[i] == null) {
continue;
@@ -1095,40 +1257,21 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
view.setLayoutParams(params);
}
- public static class DisplayCutoutView extends View implements DisplayManager.DisplayListener,
- RegionInterceptableView {
-
- private static final float HIDDEN_CAMERA_PROTECTION_SCALE = 0.5f;
-
- private Display.Mode mDisplayMode = null;
- private final DisplayInfo mInfo = new DisplayInfo();
- private final Paint mPaint = new Paint();
+ public static class DisplayCutoutView extends DisplayCutoutBaseView {
private final List mBounds = new ArrayList();
private final Rect mBoundingRect = new Rect();
- private final Path mBoundingPath = new Path();
- // Don't initialize these yet because they may never exist
- private RectF mProtectionRect;
- private RectF mProtectionRectOrig;
- private Path mProtectionPath;
- private Path mProtectionPathOrig;
private Rect mTotalBounds = new Rect();
- // Whether or not to show the cutout protection path
- private boolean mShowProtection = false;
- private final int[] mLocation = new int[2];
- private final ScreenDecorations mDecorations;
private int mColor = Color.BLACK;
private int mRotation;
private int mInitialPosition;
private int mPosition;
- private float mCameraProtectionProgress = HIDDEN_CAMERA_PROTECTION_SCALE;
- private ValueAnimator mCameraProtectionAnimator;
- public DisplayCutoutView(Context context, @BoundsPosition int pos,
- ScreenDecorations decorations) {
+ public DisplayCutoutView(Context context, @BoundsPosition int pos) {
super(context);
mInitialPosition = pos;
- mDecorations = decorations;
+ paint.setColor(mColor);
+ paint.setStyle(Paint.Style.FILL);
setId(R.id.display_cutout);
if (DEBUG) {
getViewTreeObserver().addOnDrawListener(() -> Log.i(TAG,
@@ -1138,145 +1281,31 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
public void setColor(int color) {
mColor = color;
+ paint.setColor(mColor);
invalidate();
}
@Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
- getHandler());
- update();
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- getLocationOnScreen(mLocation);
- canvas.translate(-mLocation[0], -mLocation[1]);
-
- if (!mBoundingPath.isEmpty()) {
- mPaint.setColor(mColor);
- mPaint.setStyle(Paint.Style.FILL);
- mPaint.setAntiAlias(true);
- canvas.drawPath(mBoundingPath, mPaint);
- }
- if (mCameraProtectionProgress > HIDDEN_CAMERA_PROTECTION_SCALE
- && !mProtectionRect.isEmpty()) {
- canvas.scale(mCameraProtectionProgress, mCameraProtectionProgress,
- mProtectionRect.centerX(), mProtectionRect.centerY());
- canvas.drawPath(mProtectionPath, mPaint);
- }
- }
-
- @Override
- public void onDisplayAdded(int displayId) {
- }
-
- @Override
- public void onDisplayRemoved(int displayId) {
- }
-
- @Override
- public void onDisplayChanged(int displayId) {
- Display.Mode oldMode = mDisplayMode;
- mDisplayMode = getDisplay().getMode();
-
- // Display mode hasn't meaningfully changed, we can ignore it
- if (!modeChanged(oldMode, mDisplayMode)) {
- return;
- }
-
- if (displayId == getDisplay().getDisplayId()) {
- update();
- }
- }
-
- private boolean modeChanged(Display.Mode oldMode, Display.Mode newMode) {
- if (oldMode == null) {
- return true;
- }
-
- boolean changed = false;
- changed |= oldMode.getPhysicalHeight() != newMode.getPhysicalHeight();
- changed |= oldMode.getPhysicalWidth() != newMode.getPhysicalWidth();
- // We purposely ignore refresh rate and id changes here, because we don't need to
- // invalidate for those, and they can trigger the refresh rate to increase
-
- return changed;
- }
-
- public void setRotation(int rotation) {
+ public void updateRotation(int rotation) {
mRotation = rotation;
- update();
+ updateCutout();
}
- void setProtection(Path protectionPath, Rect pathBounds) {
- if (mProtectionPathOrig == null) {
- mProtectionPathOrig = new Path();
- mProtectionPath = new Path();
- }
- mProtectionPathOrig.set(protectionPath);
- if (mProtectionRectOrig == null) {
- mProtectionRectOrig = new RectF();
- mProtectionRect = new RectF();
- }
- mProtectionRectOrig.set(pathBounds);
- }
-
- void setShowProtection(boolean shouldShow) {
- if (mShowProtection == shouldShow) {
- return;
- }
-
- mShowProtection = shouldShow;
- updateBoundingPath();
- // Delay the relayout until the end of the animation when hiding the cutout,
- // otherwise we'd clip it.
- if (mShowProtection) {
- requestLayout();
- }
- if (mCameraProtectionAnimator != null) {
- mCameraProtectionAnimator.cancel();
- }
- mCameraProtectionAnimator = ValueAnimator.ofFloat(mCameraProtectionProgress,
- mShowProtection ? 1.0f : HIDDEN_CAMERA_PROTECTION_SCALE).setDuration(750);
- mCameraProtectionAnimator.setInterpolator(Interpolators.DECELERATE_QUINT);
- mCameraProtectionAnimator.addUpdateListener(animation -> {
- mCameraProtectionProgress = (float) animation.getAnimatedValue();
- invalidate();
- });
- mCameraProtectionAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mCameraProtectionAnimator = null;
- if (!mShowProtection) {
- requestLayout();
- }
- }
- });
- mCameraProtectionAnimator.start();
- }
-
- private void update() {
- if (!isAttachedToWindow() || mDecorations.mPendingRotationChange) {
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ @Override
+ public void updateCutout() {
+ if (!isAttachedToWindow() || pendingRotationChange) {
return;
}
mPosition = getBoundPositionFromRotation(mInitialPosition, mRotation);
requestLayout();
- getDisplay().getDisplayInfo(mInfo);
+ getDisplay().getDisplayInfo(displayInfo);
mBounds.clear();
mBoundingRect.setEmpty();
- mBoundingPath.reset();
+ cutoutPath.reset();
int newVisible;
if (shouldDrawCutout(getContext()) && hasCutout()) {
- mBounds.addAll(mInfo.displayCutout.getBoundingRects());
+ mBounds.addAll(displayInfo.displayCutout.getBoundingRects());
localBounds(mBoundingRect);
updateGravity();
updateBoundingPath();
@@ -1291,10 +1320,11 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
}
private void updateBoundingPath() {
- int lw = mInfo.logicalWidth;
- int lh = mInfo.logicalHeight;
+ int lw = displayInfo.logicalWidth;
+ int lh = displayInfo.logicalHeight;
- boolean flipped = mInfo.rotation == ROTATION_90 || mInfo.rotation == ROTATION_270;
+ boolean flipped = displayInfo.rotation == ROTATION_90
+ || displayInfo.rotation == ROTATION_270;
int dw = flipped ? lh : lw;
int dh = flipped ? lw : lh;
@@ -1302,49 +1332,20 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
Path path = DisplayCutout.pathFromResources(
getResources(), getDisplay().getUniqueId(), dw, dh);
if (path != null) {
- mBoundingPath.set(path);
+ cutoutPath.set(path);
} else {
- mBoundingPath.reset();
+ cutoutPath.reset();
}
Matrix m = new Matrix();
- transformPhysicalToLogicalCoordinates(mInfo.rotation, dw, dh, m);
- mBoundingPath.transform(m);
- if (mProtectionPathOrig != null) {
- // Reset the protection path so we don't aggregate rotations
- mProtectionPath.set(mProtectionPathOrig);
- mProtectionPath.transform(m);
- m.mapRect(mProtectionRect, mProtectionRectOrig);
- }
- }
-
- private static void transformPhysicalToLogicalCoordinates(@Surface.Rotation int rotation,
- @Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out) {
- switch (rotation) {
- case ROTATION_0:
- out.reset();
- break;
- case ROTATION_90:
- out.setRotate(270);
- out.postTranslate(0, physicalWidth);
- break;
- case ROTATION_180:
- out.setRotate(180);
- out.postTranslate(physicalWidth, physicalHeight);
- break;
- case ROTATION_270:
- out.setRotate(90);
- out.postTranslate(physicalHeight, 0);
- break;
- default:
- throw new IllegalArgumentException("Unknown rotation: " + rotation);
- }
+ transformPhysicalToLogicalCoordinates(displayInfo.rotation, dw, dh, m);
+ cutoutPath.transform(m);
}
private void updateGravity() {
LayoutParams lp = getLayoutParams();
if (lp instanceof FrameLayout.LayoutParams) {
FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) lp;
- int newGravity = getGravity(mInfo.displayCutout);
+ int newGravity = getGravity(displayInfo.displayCutout);
if (flp.gravity != newGravity) {
flp.gravity = newGravity;
setLayoutParams(flp);
@@ -1353,7 +1354,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
}
private boolean hasCutout() {
- final DisplayCutout displayCutout = mInfo.displayCutout;
+ final DisplayCutout displayCutout = displayInfo.displayCutout;
if (displayCutout == null) {
return false;
}
@@ -1377,11 +1378,11 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
return;
}
- if (mShowProtection) {
+ if (showProtection) {
// Make sure that our measured height encompases the protection
mTotalBounds.union(mBoundingRect);
- mTotalBounds.union((int) mProtectionRect.left, (int) mProtectionRect.top,
- (int) mProtectionRect.right, (int) mProtectionRect.bottom);
+ mTotalBounds.union((int) protectionRect.left, (int) protectionRect.top,
+ (int) protectionRect.right, (int) protectionRect.bottom);
setMeasuredDimension(
resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0),
resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0));
@@ -1413,7 +1414,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
}
private void localBounds(Rect out) {
- DisplayCutout displayCutout = mInfo.displayCutout;
+ DisplayCutout displayCutout = displayInfo.displayCutout;
boundsFromDirection(displayCutout, getGravity(displayCutout), out);
}
@@ -1437,32 +1438,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
}
return Gravity.NO_GRAVITY;
}
-
- @Override
- public boolean shouldInterceptTouch() {
- return mInfo.displayCutout != null && getVisibility() == VISIBLE;
- }
-
- @Override
- public Region getInterceptRegion() {
- if (mInfo.displayCutout == null) {
- return null;
- }
-
- View rootView = getRootView();
- Region cutoutBounds = rectsToRegion(
- mInfo.displayCutout.getBoundingRects());
-
- // Transform to window's coordinate space
- rootView.getLocationOnScreen(mLocation);
- cutoutBounds.translate(-mLocation[0], -mLocation[1]);
-
- // Intersect with window's frame
- cutoutBounds.op(rootView.getLeft(), rootView.getTop(), rootView.getRight(),
- rootView.getBottom(), Region.Op.INTERSECT);
-
- return cutoutBounds;
- }
}
/**
@@ -1473,6 +1448,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
private final View mView;
private final int mTargetRotation;
+ // Pass -1 for ScreenDecorHwcLayer since it's a fullscreen window and has no specific
+ // position.
private final int mPosition;
private RestartingPreDrawListener(View view, @BoundsPosition int position,
@@ -1488,7 +1465,9 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
if (mTargetRotation == mRotation) {
if (DEBUG) {
- Log.i(TAG, getWindowTitleByPos(mPosition) + " already in target rot "
+ final String title = mPosition < 0 ? "ScreenDecorHwcLayer"
+ : getWindowTitleByPos(mPosition);
+ Log.i(TAG, title + " already in target rot "
+ mTargetRotation + ", allow draw without restarting it");
}
return true;
@@ -1499,7 +1478,9 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
// take effect.
updateOrientation();
if (DEBUG) {
- Log.i(TAG, getWindowTitleByPos(mPosition)
+ final String title = mPosition < 0 ? "ScreenDecorHwcLayer"
+ : getWindowTitleByPos(mPosition);
+ Log.i(TAG, title
+ " restarting listener fired, restarting draw for rot " + mRotation);
}
mView.invalidate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index 962c7fa6aea78..140142394c248 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -92,6 +92,8 @@ class PrivacyDotViewController @Inject constructor(
private val views: Sequence
get() = if (!this::tl.isInitialized) sequenceOf() else sequenceOf(tl, tr, br, bl)
+ private var showingListener: ShowingListener? = null
+
init {
contentInsetsProvider.addCallback(object : StatusBarContentInsetsChangedListener {
override fun onStatusBarContentInsetsChanged() {
@@ -132,6 +134,10 @@ class PrivacyDotViewController @Inject constructor(
uiExecutor = e
}
+ fun setShowingListener(l: ShowingListener) {
+ showingListener = l
+ }
+
fun setQsExpanded(expanded: Boolean) {
dlog("setQsExpanded $expanded")
synchronized(lock) {
@@ -176,15 +182,20 @@ class PrivacyDotViewController @Inject constructor(
.setDuration(DURATION)
.setInterpolator(Interpolators.ALPHA_OUT)
.alpha(0f)
- .withEndAction { dot.visibility = View.INVISIBLE }
+ .withEndAction {
+ dot.visibility = View.INVISIBLE
+ showingListener?.onPrivacyDotHidden(dot)
+ }
.start()
} else {
dot.visibility = View.INVISIBLE
+ showingListener?.onPrivacyDotHidden(dot)
}
}
@UiThread
private fun showDotView(dot: View, animate: Boolean) {
+ showingListener?.onPrivacyDotShown(dot)
dot.clearAnimation()
if (animate) {
dot.visibility = View.VISIBLE
@@ -320,6 +331,7 @@ class PrivacyDotViewController @Inject constructor(
@UiThread
private fun updateDesignatedCorner(newCorner: View?, shouldShowDot: Boolean) {
if (shouldShowDot) {
+ showingListener?.onPrivacyDotShown(newCorner)
newCorner?.apply {
clearAnimation()
visibility = View.VISIBLE
@@ -336,6 +348,11 @@ class PrivacyDotViewController @Inject constructor(
private fun setCornerVisibilities(vis: Int) {
views.forEach { corner ->
corner.visibility = vis
+ if (vis == View.VISIBLE) {
+ showingListener?.onPrivacyDotShown(corner)
+ } else {
+ showingListener?.onPrivacyDotHidden(corner)
+ }
}
}
@@ -555,6 +572,11 @@ class PrivacyDotViewController @Inject constructor(
)
}
}
+
+ public interface ShowingListener {
+ fun onPrivacyDotShown(v: View?)
+ fun onPrivacyDotHidden(v: View?)
+ }
}
private fun dlog(s: String) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt
new file mode 100644
index 0000000000000..e62b4e63e3d5d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2022 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.systemui
+
+import android.graphics.Canvas
+import android.graphics.Insets
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.RectF
+import android.graphics.Region
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.DisplayCutout
+import android.view.DisplayInfo
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.never
+import com.android.internal.R
+import com.android.systemui.util.mockito.eq
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class DisplayCutoutBaseViewTest : SysuiTestCase() {
+
+ @Mock private lateinit var mockCanvas: Canvas
+ @Mock private lateinit var mockRootView: View
+ @Mock private lateinit var mockDisplay: Display
+
+ private lateinit var cutoutBaseView: DisplayCutoutBaseView
+ private val cutout: DisplayCutout = DisplayCutout.Builder()
+ .setSafeInsets(Insets.of(0, 2, 0, 0))
+ .setBoundingRectTop(Rect(1, 0, 2, 2))
+ .build()
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun testBoundingRectsToRegion() {
+ setupDisplayCutoutBaseView(true /* fillCutout */, true /* hasCutout */)
+ val rect = Rect(1, 2, 3, 4)
+ assertThat(cutoutBaseView.rectsToRegion(listOf(rect)).bounds).isEqualTo(rect)
+ }
+
+ @Test
+ fun testDrawCutout_fillCutout() {
+ setupDisplayCutoutBaseView(true /* fillCutout */, true /* hasCutout */)
+ cutoutBaseView.onDraw(mockCanvas)
+
+ verify(cutoutBaseView).drawCutouts(mockCanvas)
+ }
+
+ @Test
+ fun testDrawCutout_notFillCutout() {
+ setupDisplayCutoutBaseView(false /* fillCutout */, true /* hasCutout */)
+ cutoutBaseView.onDraw(mockCanvas)
+
+ verify(cutoutBaseView, never()).drawCutouts(mockCanvas)
+ }
+
+ @Test
+ fun testShouldInterceptTouch_hasCutout() {
+ setupDisplayCutoutBaseView(true /* fillCutout */, true /* hasCutout */)
+ cutoutBaseView.updateCutout()
+
+ assertThat(cutoutBaseView.shouldInterceptTouch()).isTrue()
+ }
+
+ @Test
+ fun testShouldInterceptTouch_noCutout() {
+ setupDisplayCutoutBaseView(true /* fillCutout */, false /* hasCutout */)
+ cutoutBaseView.updateCutout()
+
+ assertThat(cutoutBaseView.shouldInterceptTouch()).isFalse()
+ }
+
+ @Test
+ fun testGetInterceptRegion_hasCutout() {
+ setupDisplayCutoutBaseView(true /* fillCutout */, true /* hasCutout */)
+ whenever(mockRootView.left).thenReturn(0)
+ whenever(mockRootView.top).thenReturn(0)
+ whenever(mockRootView.right).thenReturn(100)
+ whenever(mockRootView.bottom).thenReturn(200)
+
+ val expect = Region()
+ expect.op(cutout.boundingRectTop, Region.Op.UNION)
+ expect.op(0, 0, 100, 200, Region.Op.INTERSECT)
+
+ cutoutBaseView.updateCutout()
+
+ assertThat(cutoutBaseView.interceptRegion).isEqualTo(expect)
+ }
+
+ @Test
+ fun testGetInterceptRegion_noCutout() {
+ setupDisplayCutoutBaseView(true /* fillCutout */, false /* hasCutout */)
+ cutoutBaseView.updateCutout()
+
+ assertThat(cutoutBaseView.interceptRegion).isNull()
+ }
+
+ @Test
+ fun testCutoutProtection() {
+ setupDisplayCutoutBaseView(true /* fillCutout */, false /* hasCutout */)
+ val bounds = Rect(0, 0, 10, 10)
+ val path = Path()
+ val pathBounds = RectF(bounds)
+ path.addRect(pathBounds, Path.Direction.CCW)
+
+ context.mainExecutor.execute {
+ cutoutBaseView.setProtection(path, bounds)
+ cutoutBaseView.enableShowProtection(true)
+ }
+ waitForIdleSync()
+
+ assertThat(cutoutBaseView.protectionPath.isRect(pathBounds)).isTrue()
+ assertThat(cutoutBaseView.protectionRect).isEqualTo(pathBounds)
+ }
+
+ private fun setupDisplayCutoutBaseView(fillCutout: Boolean, hasCutout: Boolean) {
+ mContext.orCreateTestableResources.addOverride(
+ R.array.config_displayUniqueIdArray, arrayOf())
+ mContext.orCreateTestableResources.addOverride(
+ R.bool.config_fillMainBuiltInDisplayCutout, fillCutout)
+
+ cutoutBaseView = spy(DisplayCutoutBaseView(mContext))
+ whenever(cutoutBaseView.display).thenReturn(mockDisplay)
+ whenever(cutoutBaseView.rootView).thenReturn(mockRootView)
+ whenever(mockDisplay.getDisplayInfo(eq(cutoutBaseView.displayInfo))
+ ).then {
+ val info = it.getArgument(0)
+ info.displayCutout = if (hasCutout) cutout else null
+ return@then true
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 72d72c8c3b5e6..70f325158624e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -14,7 +14,6 @@
package com.android.systemui;
-import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM;
import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
import static android.view.DisplayCutout.BOUNDS_POSITION_LENGTH;
@@ -22,7 +21,7 @@ import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT;
import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
-import static com.android.systemui.ScreenDecorations.rectsToRegion;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.google.common.truth.Truth.assertThat;
@@ -32,7 +31,6 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.atLeastOnce;
@@ -49,10 +47,12 @@ import android.annotation.IdRes;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Insets;
+import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.VectorDrawable;
import android.hardware.display.DisplayManager;
+import android.hardware.graphics.common.DisplayDecorationSupport;
import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -89,7 +89,6 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
-import java.util.Collections;
@RunWithLooper
@RunWith(AndroidTestingRunner.class)
@@ -106,6 +105,8 @@ public class ScreenDecorationsTest extends SysuiTestCase {
private FakeThreadFactory mThreadFactory;
private ArrayList mDecorProviders;
@Mock
+ private Display mDisplay;
+ @Mock
private TunerService mTunerService;
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
@@ -140,14 +141,15 @@ public class ScreenDecorationsTest extends SysuiTestCase {
.getMaximumWindowMetrics();
when(mWindowManager.getMaximumWindowMetrics()).thenReturn(metrics);
mContext.addMockSystemService(WindowManager.class, mWindowManager);
-
mDisplayManager = mock(DisplayManager.class);
- Display display = mContext.getSystemService(DisplayManager.class)
- .getDisplay(DEFAULT_DISPLAY);
- when(mDisplayManager.getDisplay(anyInt())).thenReturn(display);
mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
- when(mMockTypedArray.length()).thenReturn(0);
+ spyOn(mContext);
+ when(mContext.getDisplay()).thenReturn(mDisplay);
+ // Not support hwc layer by default
+ doReturn(null).when(mDisplay).getDisplayDecorationSupport();
+
+ when(mMockTypedArray.length()).thenReturn(0);
mPrivacyDotTopLeftDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl(
R.id.privacy_dot_top_left_container,
DisplayCutout.BOUNDS_POSITION_TOP,
@@ -974,12 +976,6 @@ public class ScreenDecorationsTest extends SysuiTestCase {
assertEquals(new Point(20, 20), mScreenDecorations.mRoundedDefaultBottom);
}
- @Test
- public void testBoundingRectsToRegion() throws Exception {
- Rect rect = new Rect(1, 2, 3, 4);
- assertThat(rectsToRegion(Collections.singletonList(rect)).getBounds(), is(rect));
- }
-
@Test
public void testRegistration_From_NoOverlay_To_HasOverlays() {
doReturn(false).when(mScreenDecorations).hasOverlays();
@@ -1029,6 +1025,114 @@ public class ScreenDecorationsTest extends SysuiTestCase {
assertThat(mScreenDecorations.mIsRegistered, is(false));
}
+ @Test
+ public void testSupportHwcLayer_SwitchFrom_NotSupport() {
+ setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */,
+ 0 /* roundedPadding */, false /* multipleRadius */,
+ true /* fillCutout */, false /* privacyDot */);
+
+ // top cutout
+ final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
+
+ mScreenDecorations.start();
+ // should only inflate mOverlays when the hwc doesn't support screen decoration
+ assertNull(mScreenDecorations.mScreenDecorHwcWindow);
+ assertNotNull(mScreenDecorations.mOverlays);
+ assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]);
+ assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]);
+
+ final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
+ decorationSupport.format = PixelFormat.R_8;
+ doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport();
+ // Trigger the support hwc screen decoration change by changing the display unique id
+ mScreenDecorations.mDisplayUniqueId = "test";
+ mScreenDecorations.mDisplayListener.onDisplayChanged(1);
+
+ // should only inflate hwc layer when the hwc supports screen decoration
+ assertNotNull(mScreenDecorations.mScreenDecorHwcWindow);
+ assertNull(mScreenDecorations.mOverlays);
+ }
+
+ @Test
+ public void testNotSupportHwcLayer_SwitchFrom_Support() {
+ setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */,
+ 0 /* roundedPadding */, false /* multipleRadius */,
+ true /* fillCutout */, false /* privacyDot */);
+ final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
+ decorationSupport.format = PixelFormat.R_8;
+ doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport();
+
+ // top cutout
+ final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
+
+ mScreenDecorations.start();
+ // should only inflate hwc layer when the hwc supports screen decoration
+ assertNotNull(mScreenDecorations.mScreenDecorHwcWindow);
+ assertNull(mScreenDecorations.mOverlays);
+
+ doReturn(null).when(mDisplay).getDisplayDecorationSupport();
+ // Trigger the support hwc screen decoration change by changing the display unique id
+ mScreenDecorations.mDisplayUniqueId = "test";
+ mScreenDecorations.mDisplayListener.onDisplayChanged(1);
+
+ // should only inflate mOverlays when the hwc doesn't support screen decoration
+ assertNull(mScreenDecorations.mScreenDecorHwcWindow);
+ assertNotNull(mScreenDecorations.mOverlays);
+ assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]);
+ assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]);
+ }
+
+ @Test
+ public void testHwcLayer_noPrivacyDot() {
+ setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */,
+ 0 /* roundedPadding */, false /* multipleRadius */,
+ true /* fillCutout */, false /* privacyDot */);
+ final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
+ decorationSupport.format = PixelFormat.R_8;
+ doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport();
+
+ // top cutout
+ final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
+
+ mScreenDecorations.start();
+
+ // Should only inflate hwc layer.
+ assertNotNull(mScreenDecorations.mScreenDecorHwcWindow);
+ assertNull(mScreenDecorations.mOverlays);
+ }
+
+ @Test
+ public void testHwcLayer_PrivacyDot() {
+ setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */,
+ 0 /* roundedPadding */, false /* multipleRadius */,
+ true /* fillCutout */, true /* privacyDot */);
+ final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
+ decorationSupport.format = PixelFormat.R_8;
+ doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport();
+
+ // top cutout
+ final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
+
+ mScreenDecorations.start();
+
+ assertNotNull(mScreenDecorations.mScreenDecorHwcWindow);
+ // mOverlays are inflated but the visibility should be GONE.
+ assertNotNull(mScreenDecorations.mOverlays);
+ final View topOverlay = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView();
+ final View botOverlay = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView();
+ assertEquals(topOverlay.getVisibility(), View.INVISIBLE);
+ assertEquals(botOverlay.getVisibility(), View.INVISIBLE);
+
+ }
+
private void setupResources(int radius, int radiusTop, int radiusBottom, int roundedPadding,
boolean multipleRadius, boolean fillCutout, boolean privacyDot) {
mContext.getOrCreateTestableResources().addOverride(