Loading packages/SystemUI/res/layout/rounded_corners_bottom.xml +2 −0 Original line number Diff line number Diff line Loading @@ -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"/> <ImageView Loading @@ -32,6 +33,7 @@ android:layout_width="12dp" android:layout_height="12dp" android:tint="#ff000000" android:visibility="gone" android:layout_gravity="right|bottom" android:src="@drawable/rounded_corner_bottom"/> Loading packages/SystemUI/res/layout/rounded_corners_top.xml +2 −0 Original line number Diff line number Diff line Loading @@ -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"/> <ImageView Loading @@ -32,6 +33,7 @@ android:layout_width="12dp" android:layout_height="12dp" android:tint="#ff000000" android:visibility="gone" android:layout_gravity="right|top" android:src="@drawable/rounded_corner_top"/> Loading packages/SystemUI/res/layout/screen_decor_hwc_layer.xml 0 → 100644 +21 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ 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. --> <com.android.systemui.RegionInterceptingFrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/screen_decor_hwc_container" android:layout_width="match_parent" android:layout_height="match_parent" /> packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt 0 → 100644 +276 −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.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<Rect?>?): 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 packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt 0 → 100644 +195 −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.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 } } Loading
packages/SystemUI/res/layout/rounded_corners_bottom.xml +2 −0 Original line number Diff line number Diff line Loading @@ -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"/> <ImageView Loading @@ -32,6 +33,7 @@ android:layout_width="12dp" android:layout_height="12dp" android:tint="#ff000000" android:visibility="gone" android:layout_gravity="right|bottom" android:src="@drawable/rounded_corner_bottom"/> Loading
packages/SystemUI/res/layout/rounded_corners_top.xml +2 −0 Original line number Diff line number Diff line Loading @@ -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"/> <ImageView Loading @@ -32,6 +33,7 @@ android:layout_width="12dp" android:layout_height="12dp" android:tint="#ff000000" android:visibility="gone" android:layout_gravity="right|top" android:src="@drawable/rounded_corner_top"/> Loading
packages/SystemUI/res/layout/screen_decor_hwc_layer.xml 0 → 100644 +21 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ 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. --> <com.android.systemui.RegionInterceptingFrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/screen_decor_hwc_container" android:layout_width="match_parent" android:layout_height="match_parent" />
packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt 0 → 100644 +276 −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.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<Rect?>?): 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
packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt 0 → 100644 +195 −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.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 } }