Loading packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt +4 −3 Original line number Diff line number Diff line Loading @@ -49,7 +49,7 @@ 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 val location = IntArray(2) protected var displayRotation = 0 @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Loading @@ -65,7 +65,8 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { @JvmField val protectionPath: Path = Path() private val protectionRectOrig: RectF = RectF() private val protectionPathOrig: Path = Path() private var cameraProtectionProgress: Float = HIDDEN_CAMERA_PROTECTION_SCALE @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) var cameraProtectionProgress: Float = HIDDEN_CAMERA_PROTECTION_SCALE private var cameraProtectionAnimator: ValueAnimator? = null constructor(context: Context) : super(context) Loading packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt +183 −1 Original line number Diff line number Diff line Loading @@ -26,11 +26,22 @@ import android.graphics.PixelFormat import android.graphics.PorterDuff import android.graphics.PorterDuffColorFilter import android.graphics.PorterDuffXfermode import android.graphics.Rect import android.graphics.Region import android.graphics.drawable.Drawable import android.hardware.graphics.common.AlphaInterpretation import android.hardware.graphics.common.DisplayDecorationSupport import android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM import android.view.DisplayCutout.BOUNDS_POSITION_LEFT import android.view.DisplayCutout.BOUNDS_POSITION_LENGTH import android.view.DisplayCutout.BOUNDS_POSITION_TOP import android.view.DisplayCutout.BOUNDS_POSITION_RIGHT import android.view.RoundedCorner import android.view.RoundedCorners import android.view.Surface import androidx.annotation.VisibleForTesting import kotlin.math.ceil import kotlin.math.floor /** * When the HWC of the device supports Composition.DISPLAY_DECORATON, we use this layer to draw Loading @@ -38,13 +49,16 @@ import android.view.RoundedCorners */ class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDecorationSupport) : DisplayCutoutBaseView(context) { public val colorMode: Int 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 @JvmField val transparentRect: Rect = Rect() private val debugTransparentRegionPaint: Paint? private val tempRect: Rect = Rect() private var roundedCornerTopSize = 0 private var roundedCornerBottomSize = 0 Loading @@ -61,6 +75,10 @@ class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDec bgColor = Color.TRANSPARENT colorMode = ActivityInfo.COLOR_MODE_DEFAULT useInvertedAlphaColor = false debugTransparentRegionPaint = Paint().apply { color = 0x2f00ff00 // semi-transparent green style = Paint.Style.FILL } } else { colorMode = ActivityInfo.COLOR_MODE_A8 useInvertedAlphaColor = displayDecorationSupport.alphaInterpretation == Loading @@ -72,6 +90,7 @@ class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDec color = Color.BLACK bgColor = Color.TRANSPARENT } debugTransparentRegionPaint = null } cornerFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) cornerBgFilter = PorterDuffColorFilter(bgColor, PorterDuff.Mode.SRC_OUT) Loading @@ -82,6 +101,9 @@ class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDec override fun onAttachedToWindow() { super.onAttachedToWindow() if (!DEBUG_COLOR) { parent.requestTransparentRegion(this) } viewRootImpl.setDisplayDecoration(true) if (useInvertedAlphaColor) { Loading @@ -93,12 +115,172 @@ class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDec } override fun onDraw(canvas: Canvas) { // If updating onDraw, also update gatherTransparentRegion if (useInvertedAlphaColor) { canvas.drawColor(bgColor) } // Cutouts are drawn in DisplayCutoutBaseView.onDraw() super.onDraw(canvas) drawRoundedCorners(canvas) debugTransparentRegionPaint?.let { calculateTransparentRect() canvas.drawRect(transparentRect, it) } } override fun gatherTransparentRegion(region: Region?): Boolean { region?.let { calculateTransparentRect() region.op(transparentRect, Region.Op.INTERSECT) } // Always return false - views underneath this should always be visible. return false } /** * The transparent rect is calculated by subtracting the regions of cutouts, cutout protect and * rounded corners from the region with fullscreen display size. */ @VisibleForTesting fun calculateTransparentRect() { transparentRect.set(0, 0, width, height) // Remove cutout region. removeCutoutFromTransparentRegion() // Remove cutout protection region. removeCutoutProtectionFromTransparentRegion() // Remove rounded corner region. removeRoundedCornersFromTransparentRegion() } private fun removeCutoutFromTransparentRegion() { displayInfo.displayCutout?.let { cutout -> if (!cutout.boundingRectLeft.isEmpty) { transparentRect.left = cutout.boundingRectLeft.right.coerceAtLeast(transparentRect.left) } if (!cutout.boundingRectTop.isEmpty) { transparentRect.top = cutout.boundingRectTop.bottom.coerceAtLeast(transparentRect.top) } if (!cutout.boundingRectRight.isEmpty) { transparentRect.right = cutout.boundingRectRight.left.coerceAtMost(transparentRect.right) } if (!cutout.boundingRectBottom.isEmpty) { transparentRect.bottom = cutout.boundingRectBottom.top.coerceAtMost(transparentRect.bottom) } } } private fun removeCutoutProtectionFromTransparentRegion() { if (protectionRect.isEmpty) { return } val centerX = protectionRect.centerX() val centerY = protectionRect.centerY() val scaledDistanceX = (centerX - protectionRect.left) * cameraProtectionProgress val scaledDistanceY = (centerY - protectionRect.top) * cameraProtectionProgress tempRect.set( floor(centerX - scaledDistanceX).toInt(), floor(centerY - scaledDistanceY).toInt(), ceil(centerX + scaledDistanceX).toInt(), ceil(centerY + scaledDistanceY).toInt() ) // Find out which edge the protectionRect belongs and remove that edge from the transparent // region. val leftDistance = tempRect.left val topDistance = tempRect.top val rightDistance = width - tempRect.right val bottomDistance = height - tempRect.bottom val minDistance = minOf(leftDistance, topDistance, rightDistance, bottomDistance) when (minDistance) { leftDistance -> { transparentRect.left = tempRect.right.coerceAtLeast(transparentRect.left) } topDistance -> { transparentRect.top = tempRect.bottom.coerceAtLeast(transparentRect.top) } rightDistance -> { transparentRect.right = tempRect.left.coerceAtMost(transparentRect.right) } bottomDistance -> { transparentRect.bottom = tempRect.top.coerceAtMost(transparentRect.bottom) } } } private fun removeRoundedCornersFromTransparentRegion() { var hasTopOrBottomCutouts = false var hasLeftOrRightCutouts = false displayInfo.displayCutout?.let { cutout -> hasTopOrBottomCutouts = !cutout.boundingRectTop.isEmpty || !cutout.boundingRectBottom.isEmpty hasLeftOrRightCutouts = !cutout.boundingRectLeft.isEmpty || !cutout.boundingRectRight.isEmpty } // The goal is to remove the rounded corner areas as small as possible so that we can have a // larger transparent region. Therefore, we should always remove from the short edge sides // if possible. val isShortEdgeTopBottom = width < height if (isShortEdgeTopBottom) { // Short edges on top & bottom. if (!hasTopOrBottomCutouts && hasLeftOrRightCutouts) { // If there are cutouts only on left or right edges, remove left and right sides // for rounded corners. transparentRect.left = getRoundedCornerSizeByPosition(BOUNDS_POSITION_LEFT) .coerceAtLeast(transparentRect.left) transparentRect.right = (width - getRoundedCornerSizeByPosition(BOUNDS_POSITION_RIGHT)) .coerceAtMost(transparentRect.right) } else { // If there are cutouts on top or bottom edges or no cutout at all, remove top // and bottom sides for rounded corners. transparentRect.top = getRoundedCornerSizeByPosition(BOUNDS_POSITION_TOP) .coerceAtLeast(transparentRect.top) transparentRect.bottom = (height - getRoundedCornerSizeByPosition(BOUNDS_POSITION_BOTTOM)) .coerceAtMost(transparentRect.bottom) } } else { // Short edges on left & right. if (hasTopOrBottomCutouts && !hasLeftOrRightCutouts) { // If there are cutouts only on top or bottom edges, remove top and bottom sides // for rounded corners. transparentRect.top = getRoundedCornerSizeByPosition(BOUNDS_POSITION_TOP) .coerceAtLeast(transparentRect.top) transparentRect.bottom = (height - getRoundedCornerSizeByPosition(BOUNDS_POSITION_BOTTOM)) .coerceAtMost(transparentRect.bottom) } else { // If there are cutouts on left or right edges or no cutout at all, remove left // and right sides for rounded corners. transparentRect.left = getRoundedCornerSizeByPosition(BOUNDS_POSITION_LEFT) .coerceAtLeast(transparentRect.left) transparentRect.right = (width - getRoundedCornerSizeByPosition(BOUNDS_POSITION_RIGHT)) .coerceAtMost(transparentRect.right) } } } private fun getRoundedCornerSizeByPosition(position: Int): Int { val delta = displayRotation - Surface.ROTATION_0 return when ((position + delta) % BOUNDS_POSITION_LENGTH) { BOUNDS_POSITION_LEFT -> roundedCornerTopSize.coerceAtLeast(roundedCornerBottomSize) BOUNDS_POSITION_TOP -> roundedCornerTopSize BOUNDS_POSITION_RIGHT -> roundedCornerTopSize.coerceAtLeast(roundedCornerBottomSize) BOUNDS_POSITION_BOTTOM -> roundedCornerBottomSize else -> throw IllegalArgumentException("Incorrect position: $position") } } private fun drawRoundedCorners(canvas: Canvas) { Loading packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +12 −6 Original line number Diff line number Diff line Loading @@ -847,14 +847,20 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab pw.println(" mIsRoundedCornerMultipleRadius:" + mIsRoundedCornerMultipleRadius); pw.println(" mIsPrivacyDotEnabled:" + isPrivacyDotEnabled()); pw.println(" mPendingRotationChange:" + mPendingRotationChange); if (mHwcScreenDecorationSupport != null) { pw.println(" mHwcScreenDecorationSupport:"); if (mHwcScreenDecorationSupport == null) { pw.println(" null"); } else { pw.println(" format: " pw.println(" format=" + PixelFormat.formatToString(mHwcScreenDecorationSupport.format)); pw.println(" alphaInterpretation: " pw.println(" alphaInterpretation=" + alphaInterpretationToString(mHwcScreenDecorationSupport.alphaInterpretation)); } else { pw.println(" mHwcScreenDecorationSupport: null"); } if (mScreenDecorHwcLayer != null) { pw.println(" mScreenDecorHwcLayer:"); pw.println(" transparentRegion=" + mScreenDecorHwcLayer.transparentRect); } else { pw.println(" mScreenDecorHwcLayer: null"); } pw.println(" mRoundedDefault(x,y)=(" + mRoundedDefault.x + "," + mRoundedDefault.y + ")"); pw.println(" mRoundedDefaultTop(x,y)=(" + mRoundedDefaultTop.x + "," + mRoundedDefaultTop.y Loading packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt 0 → 100644 +182 −0 Original line number Diff line number Diff line /* * 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.Insets import android.graphics.PixelFormat import android.graphics.Rect import android.graphics.RectF import android.hardware.graphics.common.DisplayDecorationSupport 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.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 import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever @RunWith(AndroidTestingRunner::class) @SmallTest class ScreenDecorHwcLayerTest : SysuiTestCase() { @Mock private lateinit var mockDisplay: Display @Mock private lateinit var mockRootView: View private val displayWidth = 100 private val displayHeight = 200 private val cutoutSize = 10 private val roundedSizeTop = 15 private val roundedSizeBottom = 20 private lateinit var decorHwcLayer: ScreenDecorHwcLayer private val cutoutTop: DisplayCutout = DisplayCutout.Builder() .setSafeInsets(Insets.of(0, cutoutSize, 0, 0)) .setBoundingRectTop(Rect(1, 0, 2, cutoutSize)) .build() private val cutoutRight: DisplayCutout = DisplayCutout.Builder() .setSafeInsets(Insets.of(0, 0, cutoutSize, 0)) .setBoundingRectRight(Rect(displayWidth - cutoutSize, 50, displayWidth, 52)) .build() @Before fun setUp() { MockitoAnnotations.initMocks(this) mContext.orCreateTestableResources.addOverride( R.array.config_displayUniqueIdArray, arrayOf<String>()) mContext.orCreateTestableResources.addOverride( R.bool.config_fillMainBuiltInDisplayCutout, true) val decorationSupport = DisplayDecorationSupport() decorationSupport.format = PixelFormat.R_8 decorHwcLayer = Mockito.spy(ScreenDecorHwcLayer(mContext, decorationSupport)) whenever(decorHwcLayer.width).thenReturn(displayWidth) whenever(decorHwcLayer.height).thenReturn(displayHeight) whenever(decorHwcLayer.display).thenReturn(mockDisplay) whenever(decorHwcLayer.rootView).thenReturn(mockRootView) whenever(mockRootView.left).thenReturn(0) whenever(mockRootView.top).thenReturn(0) whenever(mockRootView.right).thenReturn(displayWidth) whenever(mockRootView.bottom).thenReturn(displayHeight) } @Test fun testTransparentRegion_noCutout_noRoundedCorner_noProtection() { setupConfigs(null, 0, 0, RectF(), 0f) decorHwcLayer.calculateTransparentRect() assertThat(decorHwcLayer.transparentRect) .isEqualTo(Rect(0, 0, decorHwcLayer.width, decorHwcLayer.height)) } @Test fun testTransparentRegion_onlyShortEdgeCutout() { setupConfigs(cutoutTop, 0, 0, RectF(), 0f) decorHwcLayer.calculateTransparentRect() assertThat(decorHwcLayer.transparentRect) .isEqualTo(Rect(0, cutoutSize, decorHwcLayer.width, decorHwcLayer.height)) } @Test fun testTransparentRegion_onlyLongEdgeCutout() { setupConfigs(cutoutRight, 0, 0, RectF(), 0f) decorHwcLayer.calculateTransparentRect() assertThat(decorHwcLayer.transparentRect) .isEqualTo(Rect(0, 0, decorHwcLayer.width - cutoutSize, decorHwcLayer.height)) } @Test fun testTransparentRegion_onlyRoundedCorners() { setupConfigs(null, roundedSizeTop, roundedSizeBottom, RectF(), 0f) decorHwcLayer.calculateTransparentRect() assertThat(decorHwcLayer.transparentRect) .isEqualTo(Rect(0, roundedSizeTop, decorHwcLayer.width, decorHwcLayer.height - roundedSizeBottom)) } @Test fun testTransparentRegion_onlyCutoutProtection() { setupConfigs(null, 0, 0, RectF(48f, 1f, 52f, 5f), 0.5f) decorHwcLayer.calculateTransparentRect() assertThat(decorHwcLayer.transparentRect) .isEqualTo(Rect(0, 4, decorHwcLayer.width, decorHwcLayer.height)) decorHwcLayer.cameraProtectionProgress = 1f decorHwcLayer.calculateTransparentRect() assertThat(decorHwcLayer.transparentRect) .isEqualTo(Rect(0, 5, decorHwcLayer.width, decorHwcLayer.height)) } @Test fun testTransparentRegion_hasShortEdgeCutout_hasRoundedCorner_hasCutoutProtection() { setupConfigs(cutoutTop, roundedSizeTop, roundedSizeBottom, RectF(48f, 1f, 52f, 5f), 1f) decorHwcLayer.calculateTransparentRect() assertThat(decorHwcLayer.transparentRect) .isEqualTo(Rect(0, 15, decorHwcLayer.width, decorHwcLayer.height - 20)) } @Test fun testTransparentRegion_hasLongEdgeCutout_hasRoundedCorner_hasCutoutProtection() { setupConfigs(cutoutRight, roundedSizeTop, roundedSizeBottom, RectF(48f, 1f, 52f, 5f), 1f) decorHwcLayer.calculateTransparentRect() assertThat(decorHwcLayer.transparentRect) .isEqualTo(Rect(20, 5, decorHwcLayer.width - 20, decorHwcLayer.height)) } private fun setupConfigs( cutout: DisplayCutout?, roundedTop: Int, roundedBottom: Int, protectionRect: RectF, protectionProgress: Float ) { whenever(mockDisplay.getDisplayInfo(eq(decorHwcLayer.displayInfo)) ).then { val info = it.getArgument<DisplayInfo>(0) info.displayCutout = cutout return@then true } decorHwcLayer.updateRoundedCornerSize(roundedTop, roundedBottom) decorHwcLayer.protectionRect.set(protectionRect) decorHwcLayer.cameraProtectionProgress = protectionProgress decorHwcLayer.updateCutout() } } Loading
packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt +4 −3 Original line number Diff line number Diff line Loading @@ -49,7 +49,7 @@ 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 val location = IntArray(2) protected var displayRotation = 0 @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Loading @@ -65,7 +65,8 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { @JvmField val protectionPath: Path = Path() private val protectionRectOrig: RectF = RectF() private val protectionPathOrig: Path = Path() private var cameraProtectionProgress: Float = HIDDEN_CAMERA_PROTECTION_SCALE @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) var cameraProtectionProgress: Float = HIDDEN_CAMERA_PROTECTION_SCALE private var cameraProtectionAnimator: ValueAnimator? = null constructor(context: Context) : super(context) Loading
packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt +183 −1 Original line number Diff line number Diff line Loading @@ -26,11 +26,22 @@ import android.graphics.PixelFormat import android.graphics.PorterDuff import android.graphics.PorterDuffColorFilter import android.graphics.PorterDuffXfermode import android.graphics.Rect import android.graphics.Region import android.graphics.drawable.Drawable import android.hardware.graphics.common.AlphaInterpretation import android.hardware.graphics.common.DisplayDecorationSupport import android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM import android.view.DisplayCutout.BOUNDS_POSITION_LEFT import android.view.DisplayCutout.BOUNDS_POSITION_LENGTH import android.view.DisplayCutout.BOUNDS_POSITION_TOP import android.view.DisplayCutout.BOUNDS_POSITION_RIGHT import android.view.RoundedCorner import android.view.RoundedCorners import android.view.Surface import androidx.annotation.VisibleForTesting import kotlin.math.ceil import kotlin.math.floor /** * When the HWC of the device supports Composition.DISPLAY_DECORATON, we use this layer to draw Loading @@ -38,13 +49,16 @@ import android.view.RoundedCorners */ class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDecorationSupport) : DisplayCutoutBaseView(context) { public val colorMode: Int 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 @JvmField val transparentRect: Rect = Rect() private val debugTransparentRegionPaint: Paint? private val tempRect: Rect = Rect() private var roundedCornerTopSize = 0 private var roundedCornerBottomSize = 0 Loading @@ -61,6 +75,10 @@ class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDec bgColor = Color.TRANSPARENT colorMode = ActivityInfo.COLOR_MODE_DEFAULT useInvertedAlphaColor = false debugTransparentRegionPaint = Paint().apply { color = 0x2f00ff00 // semi-transparent green style = Paint.Style.FILL } } else { colorMode = ActivityInfo.COLOR_MODE_A8 useInvertedAlphaColor = displayDecorationSupport.alphaInterpretation == Loading @@ -72,6 +90,7 @@ class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDec color = Color.BLACK bgColor = Color.TRANSPARENT } debugTransparentRegionPaint = null } cornerFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) cornerBgFilter = PorterDuffColorFilter(bgColor, PorterDuff.Mode.SRC_OUT) Loading @@ -82,6 +101,9 @@ class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDec override fun onAttachedToWindow() { super.onAttachedToWindow() if (!DEBUG_COLOR) { parent.requestTransparentRegion(this) } viewRootImpl.setDisplayDecoration(true) if (useInvertedAlphaColor) { Loading @@ -93,12 +115,172 @@ class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDec } override fun onDraw(canvas: Canvas) { // If updating onDraw, also update gatherTransparentRegion if (useInvertedAlphaColor) { canvas.drawColor(bgColor) } // Cutouts are drawn in DisplayCutoutBaseView.onDraw() super.onDraw(canvas) drawRoundedCorners(canvas) debugTransparentRegionPaint?.let { calculateTransparentRect() canvas.drawRect(transparentRect, it) } } override fun gatherTransparentRegion(region: Region?): Boolean { region?.let { calculateTransparentRect() region.op(transparentRect, Region.Op.INTERSECT) } // Always return false - views underneath this should always be visible. return false } /** * The transparent rect is calculated by subtracting the regions of cutouts, cutout protect and * rounded corners from the region with fullscreen display size. */ @VisibleForTesting fun calculateTransparentRect() { transparentRect.set(0, 0, width, height) // Remove cutout region. removeCutoutFromTransparentRegion() // Remove cutout protection region. removeCutoutProtectionFromTransparentRegion() // Remove rounded corner region. removeRoundedCornersFromTransparentRegion() } private fun removeCutoutFromTransparentRegion() { displayInfo.displayCutout?.let { cutout -> if (!cutout.boundingRectLeft.isEmpty) { transparentRect.left = cutout.boundingRectLeft.right.coerceAtLeast(transparentRect.left) } if (!cutout.boundingRectTop.isEmpty) { transparentRect.top = cutout.boundingRectTop.bottom.coerceAtLeast(transparentRect.top) } if (!cutout.boundingRectRight.isEmpty) { transparentRect.right = cutout.boundingRectRight.left.coerceAtMost(transparentRect.right) } if (!cutout.boundingRectBottom.isEmpty) { transparentRect.bottom = cutout.boundingRectBottom.top.coerceAtMost(transparentRect.bottom) } } } private fun removeCutoutProtectionFromTransparentRegion() { if (protectionRect.isEmpty) { return } val centerX = protectionRect.centerX() val centerY = protectionRect.centerY() val scaledDistanceX = (centerX - protectionRect.left) * cameraProtectionProgress val scaledDistanceY = (centerY - protectionRect.top) * cameraProtectionProgress tempRect.set( floor(centerX - scaledDistanceX).toInt(), floor(centerY - scaledDistanceY).toInt(), ceil(centerX + scaledDistanceX).toInt(), ceil(centerY + scaledDistanceY).toInt() ) // Find out which edge the protectionRect belongs and remove that edge from the transparent // region. val leftDistance = tempRect.left val topDistance = tempRect.top val rightDistance = width - tempRect.right val bottomDistance = height - tempRect.bottom val minDistance = minOf(leftDistance, topDistance, rightDistance, bottomDistance) when (minDistance) { leftDistance -> { transparentRect.left = tempRect.right.coerceAtLeast(transparentRect.left) } topDistance -> { transparentRect.top = tempRect.bottom.coerceAtLeast(transparentRect.top) } rightDistance -> { transparentRect.right = tempRect.left.coerceAtMost(transparentRect.right) } bottomDistance -> { transparentRect.bottom = tempRect.top.coerceAtMost(transparentRect.bottom) } } } private fun removeRoundedCornersFromTransparentRegion() { var hasTopOrBottomCutouts = false var hasLeftOrRightCutouts = false displayInfo.displayCutout?.let { cutout -> hasTopOrBottomCutouts = !cutout.boundingRectTop.isEmpty || !cutout.boundingRectBottom.isEmpty hasLeftOrRightCutouts = !cutout.boundingRectLeft.isEmpty || !cutout.boundingRectRight.isEmpty } // The goal is to remove the rounded corner areas as small as possible so that we can have a // larger transparent region. Therefore, we should always remove from the short edge sides // if possible. val isShortEdgeTopBottom = width < height if (isShortEdgeTopBottom) { // Short edges on top & bottom. if (!hasTopOrBottomCutouts && hasLeftOrRightCutouts) { // If there are cutouts only on left or right edges, remove left and right sides // for rounded corners. transparentRect.left = getRoundedCornerSizeByPosition(BOUNDS_POSITION_LEFT) .coerceAtLeast(transparentRect.left) transparentRect.right = (width - getRoundedCornerSizeByPosition(BOUNDS_POSITION_RIGHT)) .coerceAtMost(transparentRect.right) } else { // If there are cutouts on top or bottom edges or no cutout at all, remove top // and bottom sides for rounded corners. transparentRect.top = getRoundedCornerSizeByPosition(BOUNDS_POSITION_TOP) .coerceAtLeast(transparentRect.top) transparentRect.bottom = (height - getRoundedCornerSizeByPosition(BOUNDS_POSITION_BOTTOM)) .coerceAtMost(transparentRect.bottom) } } else { // Short edges on left & right. if (hasTopOrBottomCutouts && !hasLeftOrRightCutouts) { // If there are cutouts only on top or bottom edges, remove top and bottom sides // for rounded corners. transparentRect.top = getRoundedCornerSizeByPosition(BOUNDS_POSITION_TOP) .coerceAtLeast(transparentRect.top) transparentRect.bottom = (height - getRoundedCornerSizeByPosition(BOUNDS_POSITION_BOTTOM)) .coerceAtMost(transparentRect.bottom) } else { // If there are cutouts on left or right edges or no cutout at all, remove left // and right sides for rounded corners. transparentRect.left = getRoundedCornerSizeByPosition(BOUNDS_POSITION_LEFT) .coerceAtLeast(transparentRect.left) transparentRect.right = (width - getRoundedCornerSizeByPosition(BOUNDS_POSITION_RIGHT)) .coerceAtMost(transparentRect.right) } } } private fun getRoundedCornerSizeByPosition(position: Int): Int { val delta = displayRotation - Surface.ROTATION_0 return when ((position + delta) % BOUNDS_POSITION_LENGTH) { BOUNDS_POSITION_LEFT -> roundedCornerTopSize.coerceAtLeast(roundedCornerBottomSize) BOUNDS_POSITION_TOP -> roundedCornerTopSize BOUNDS_POSITION_RIGHT -> roundedCornerTopSize.coerceAtLeast(roundedCornerBottomSize) BOUNDS_POSITION_BOTTOM -> roundedCornerBottomSize else -> throw IllegalArgumentException("Incorrect position: $position") } } private fun drawRoundedCorners(canvas: Canvas) { Loading
packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +12 −6 Original line number Diff line number Diff line Loading @@ -847,14 +847,20 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab pw.println(" mIsRoundedCornerMultipleRadius:" + mIsRoundedCornerMultipleRadius); pw.println(" mIsPrivacyDotEnabled:" + isPrivacyDotEnabled()); pw.println(" mPendingRotationChange:" + mPendingRotationChange); if (mHwcScreenDecorationSupport != null) { pw.println(" mHwcScreenDecorationSupport:"); if (mHwcScreenDecorationSupport == null) { pw.println(" null"); } else { pw.println(" format: " pw.println(" format=" + PixelFormat.formatToString(mHwcScreenDecorationSupport.format)); pw.println(" alphaInterpretation: " pw.println(" alphaInterpretation=" + alphaInterpretationToString(mHwcScreenDecorationSupport.alphaInterpretation)); } else { pw.println(" mHwcScreenDecorationSupport: null"); } if (mScreenDecorHwcLayer != null) { pw.println(" mScreenDecorHwcLayer:"); pw.println(" transparentRegion=" + mScreenDecorHwcLayer.transparentRect); } else { pw.println(" mScreenDecorHwcLayer: null"); } pw.println(" mRoundedDefault(x,y)=(" + mRoundedDefault.x + "," + mRoundedDefault.y + ")"); pw.println(" mRoundedDefaultTop(x,y)=(" + mRoundedDefaultTop.x + "," + mRoundedDefaultTop.y Loading
packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt 0 → 100644 +182 −0 Original line number Diff line number Diff line /* * 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.Insets import android.graphics.PixelFormat import android.graphics.Rect import android.graphics.RectF import android.hardware.graphics.common.DisplayDecorationSupport 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.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 import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever @RunWith(AndroidTestingRunner::class) @SmallTest class ScreenDecorHwcLayerTest : SysuiTestCase() { @Mock private lateinit var mockDisplay: Display @Mock private lateinit var mockRootView: View private val displayWidth = 100 private val displayHeight = 200 private val cutoutSize = 10 private val roundedSizeTop = 15 private val roundedSizeBottom = 20 private lateinit var decorHwcLayer: ScreenDecorHwcLayer private val cutoutTop: DisplayCutout = DisplayCutout.Builder() .setSafeInsets(Insets.of(0, cutoutSize, 0, 0)) .setBoundingRectTop(Rect(1, 0, 2, cutoutSize)) .build() private val cutoutRight: DisplayCutout = DisplayCutout.Builder() .setSafeInsets(Insets.of(0, 0, cutoutSize, 0)) .setBoundingRectRight(Rect(displayWidth - cutoutSize, 50, displayWidth, 52)) .build() @Before fun setUp() { MockitoAnnotations.initMocks(this) mContext.orCreateTestableResources.addOverride( R.array.config_displayUniqueIdArray, arrayOf<String>()) mContext.orCreateTestableResources.addOverride( R.bool.config_fillMainBuiltInDisplayCutout, true) val decorationSupport = DisplayDecorationSupport() decorationSupport.format = PixelFormat.R_8 decorHwcLayer = Mockito.spy(ScreenDecorHwcLayer(mContext, decorationSupport)) whenever(decorHwcLayer.width).thenReturn(displayWidth) whenever(decorHwcLayer.height).thenReturn(displayHeight) whenever(decorHwcLayer.display).thenReturn(mockDisplay) whenever(decorHwcLayer.rootView).thenReturn(mockRootView) whenever(mockRootView.left).thenReturn(0) whenever(mockRootView.top).thenReturn(0) whenever(mockRootView.right).thenReturn(displayWidth) whenever(mockRootView.bottom).thenReturn(displayHeight) } @Test fun testTransparentRegion_noCutout_noRoundedCorner_noProtection() { setupConfigs(null, 0, 0, RectF(), 0f) decorHwcLayer.calculateTransparentRect() assertThat(decorHwcLayer.transparentRect) .isEqualTo(Rect(0, 0, decorHwcLayer.width, decorHwcLayer.height)) } @Test fun testTransparentRegion_onlyShortEdgeCutout() { setupConfigs(cutoutTop, 0, 0, RectF(), 0f) decorHwcLayer.calculateTransparentRect() assertThat(decorHwcLayer.transparentRect) .isEqualTo(Rect(0, cutoutSize, decorHwcLayer.width, decorHwcLayer.height)) } @Test fun testTransparentRegion_onlyLongEdgeCutout() { setupConfigs(cutoutRight, 0, 0, RectF(), 0f) decorHwcLayer.calculateTransparentRect() assertThat(decorHwcLayer.transparentRect) .isEqualTo(Rect(0, 0, decorHwcLayer.width - cutoutSize, decorHwcLayer.height)) } @Test fun testTransparentRegion_onlyRoundedCorners() { setupConfigs(null, roundedSizeTop, roundedSizeBottom, RectF(), 0f) decorHwcLayer.calculateTransparentRect() assertThat(decorHwcLayer.transparentRect) .isEqualTo(Rect(0, roundedSizeTop, decorHwcLayer.width, decorHwcLayer.height - roundedSizeBottom)) } @Test fun testTransparentRegion_onlyCutoutProtection() { setupConfigs(null, 0, 0, RectF(48f, 1f, 52f, 5f), 0.5f) decorHwcLayer.calculateTransparentRect() assertThat(decorHwcLayer.transparentRect) .isEqualTo(Rect(0, 4, decorHwcLayer.width, decorHwcLayer.height)) decorHwcLayer.cameraProtectionProgress = 1f decorHwcLayer.calculateTransparentRect() assertThat(decorHwcLayer.transparentRect) .isEqualTo(Rect(0, 5, decorHwcLayer.width, decorHwcLayer.height)) } @Test fun testTransparentRegion_hasShortEdgeCutout_hasRoundedCorner_hasCutoutProtection() { setupConfigs(cutoutTop, roundedSizeTop, roundedSizeBottom, RectF(48f, 1f, 52f, 5f), 1f) decorHwcLayer.calculateTransparentRect() assertThat(decorHwcLayer.transparentRect) .isEqualTo(Rect(0, 15, decorHwcLayer.width, decorHwcLayer.height - 20)) } @Test fun testTransparentRegion_hasLongEdgeCutout_hasRoundedCorner_hasCutoutProtection() { setupConfigs(cutoutRight, roundedSizeTop, roundedSizeBottom, RectF(48f, 1f, 52f, 5f), 1f) decorHwcLayer.calculateTransparentRect() assertThat(decorHwcLayer.transparentRect) .isEqualTo(Rect(20, 5, decorHwcLayer.width - 20, decorHwcLayer.height)) } private fun setupConfigs( cutout: DisplayCutout?, roundedTop: Int, roundedBottom: Int, protectionRect: RectF, protectionProgress: Float ) { whenever(mockDisplay.getDisplayInfo(eq(decorHwcLayer.displayInfo)) ).then { val info = it.getArgument<DisplayInfo>(0) info.displayCutout = cutout return@then true } decorHwcLayer.updateRoundedCornerSize(roundedTop, roundedBottom) decorHwcLayer.protectionRect.set(protectionRect) decorHwcLayer.cameraProtectionProgress = protectionProgress decorHwcLayer.updateCutout() } }