Camera Protection Info - Take into account rotation for bounds

Before, we were always returning the original not rotated bounds of the
camera protection.
With this change, the bounds will be adjusted/rotated to the correct
position, based on the current display rotation.
This is the same behaviour as the DisplayCutout. Its bounds are also
adjusted based on the current rotation.

Test: SysUICutoutProviderTest
Test: Manually
Fixes: 324405921
Flag: NONE
Change-Id: I7945130557bb10e405bf28d74c855e5d31c731cc
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index aa6b4ac..c804fc6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -1138,7 +1138,7 @@
 
     private fun setCameraProtectionBounds(protectionBounds: Rect) {
         val protectionInfo =
-            mock<CameraProtectionInfo> { whenever(this.cutoutBounds).thenReturn(protectionBounds) }
+            mock<CameraProtectionInfo> { whenever(this.bounds).thenReturn(protectionBounds) }
         whenever(sysUICutout.cameraProtection).thenReturn(protectionInfo)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
index e8499d3..ca83724 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
@@ -143,7 +143,7 @@
 
     private fun notifyCameraActive(info: CameraProtectionInfo) {
         listeners.forEach {
-            it.onApplyCameraProtection(info.cutoutProtectionPath, info.cutoutBounds)
+            it.onApplyCameraProtection(info.cutoutProtectionPath, info.bounds)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
index 6314bd9..9357056 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
@@ -23,6 +23,6 @@
     val logicalCameraId: String,
     val physicalCameraId: String?,
     val cutoutProtectionPath: Path,
-    val cutoutBounds: Rect,
+    val bounds: Rect,
     val displayUniqueId: String?,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt b/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt
index aad9341..7309599 100644
--- a/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt
@@ -17,8 +17,12 @@
 package com.android.systemui
 
 import android.content.Context
+import android.graphics.Rect
+import android.util.RotationUtils
+import android.view.Display
 import android.view.DisplayCutout
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.display.naturalBounds
 import javax.inject.Inject
 
 @SysUISingleton
@@ -33,15 +37,43 @@
         cameraProtectionLoader.loadCameraProtectionInfoList()
     }
 
-    fun cutoutInfoForCurrentDisplay(): SysUICutoutInformation? {
+    /**
+     * Returns the [SysUICutoutInformation] for the current display and the current rotation.
+     *
+     * This means that the bounds of the display cutout and the camera protection will be
+     * adjusted/rotated for the current rotation.
+     */
+    fun cutoutInfoForCurrentDisplayAndRotation(): SysUICutoutInformation? {
         val display = context.display
         val displayCutout: DisplayCutout = display.cutout ?: return null
+        return SysUICutoutInformation(displayCutout, getCameraProtectionForDisplay(display))
+    }
+
+    private fun getCameraProtectionForDisplay(display: Display): CameraProtectionInfo? {
         val displayUniqueId: String? = display.uniqueId
         if (displayUniqueId.isNullOrEmpty()) {
-            return SysUICutoutInformation(displayCutout, cameraProtection = null)
+            return null
         }
-        val cameraProtection: CameraProtectionInfo? =
+        val cameraProtection: CameraProtectionInfo =
             cameraProtectionList.firstOrNull { it.displayUniqueId == displayUniqueId }
-        return SysUICutoutInformation(displayCutout, cameraProtection)
+                ?: return null
+        val adjustedBoundsForRotation =
+            calculateCameraProtectionBoundsForRotation(display, cameraProtection.bounds)
+        return cameraProtection.copy(bounds = adjustedBoundsForRotation)
+    }
+
+    private fun calculateCameraProtectionBoundsForRotation(
+        display: Display,
+        originalProtectionBounds: Rect,
+    ): Rect {
+        val displayNaturalBounds = display.naturalBounds
+        val rotatedBoundsOut = Rect(originalProtectionBounds)
+        RotationUtils.rotateBounds(
+            /* inOutBounds = */ rotatedBoundsOut,
+            /* parentWidth = */ displayNaturalBounds.width(),
+            /* parentHeight = */ displayNaturalBounds.height(),
+            /* rotation = */ display.rotation
+        )
+        return rotatedBoundsOut
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayExtensions.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayExtensions.kt
new file mode 100644
index 0000000..0482bd8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/DisplayExtensions.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 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.display
+
+import android.graphics.Rect
+import android.view.Display
+import android.view.DisplayInfo
+
+val Display.naturalBounds: Rect
+    get() {
+        val outDisplayInfo = DisplayInfo()
+        getDisplayInfo(outDisplayInfo)
+        return Rect(
+            /* left = */ 0,
+            /* top = */ 0,
+            /* right = */ outDisplayInfo.naturalWidth,
+            /* bottom = */ outDisplayInfo.naturalHeight
+        )
+    }
+
+val Display.naturalWidth: Int
+    get() {
+        val outDisplayInfo = DisplayInfo()
+        getDisplayInfo(outDisplayInfo)
+        return outDisplayInfo.naturalWidth
+    }
+
+val Display.naturalHeight: Int
+    get() {
+        val outDisplayInfo = DisplayInfo()
+        getDisplayInfo(outDisplayInfo)
+        return outDisplayInfo.naturalHeight
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index 323ab80..613efaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -185,7 +185,7 @@
      */
     fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Insets =
         traceSection(tag = "StatusBarContentInsetsProvider.getStatusBarContentInsetsForRotation") {
-            val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplay()
+            val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplayAndRotation()
             val displayCutout = sysUICutout?.cutout
             val key = getCacheKey(rotation, displayCutout)
 
@@ -227,7 +227,7 @@
      */
     @JvmOverloads
     fun getStatusBarContentAreaForRotation(@Rotation rotation: Int): Rect {
-        val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplay()
+        val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplayAndRotation()
         val displayCutout = sysUICutout?.cutout
         val key = getCacheKey(rotation, displayCutout)
         return insetsCache[key]
@@ -528,7 +528,7 @@
     var leftMargin = minLeft
     var rightMargin = minRight
     for (cutoutRect in cutoutRects) {
-        val protectionRect = sysUICutout.cameraProtection?.cutoutBounds
+        val protectionRect = sysUICutout.cameraProtection?.bounds
         val actualCutoutRect =
             if (protectionRect?.intersects(cutoutRect) == true) {
                 rectUnion(cutoutRect, protectionRect)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
index a19a0c7..d2a17c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
@@ -89,7 +89,7 @@
         loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
 
     private fun CameraProtectionInfo.toTestableVersion() =
-        TestableProtectionInfo(logicalCameraId, physicalCameraId, cutoutBounds, displayUniqueId)
+        TestableProtectionInfo(logicalCameraId, physicalCameraId, bounds, displayUniqueId)
 
     /**
      * "Testable" version, because the original version contains a Path property, which doesn't
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt b/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
index f769b4e..6cb77cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui
 
+import android.graphics.Rect
 import com.android.systemui.res.R
 
 class FakeCameraProtectionLoader(private val context: SysuiTestableContext) :
@@ -36,7 +37,10 @@
         addInnerCameraProtection()
     }
 
-    fun addOuterCameraProtection(displayUniqueId: String = "111") {
+    fun addOuterCameraProtection(
+        displayUniqueId: String = "111",
+        bounds: Rect = Rect(/* left = */ 0, /* top = */ 0, /* right = */ 10, /* bottom = */ 10)
+    ) {
         context.orCreateTestableResources.addOverride(R.string.config_protectedCameraId, "1")
         context.orCreateTestableResources.addOverride(
             R.string.config_protectedPhysicalCameraId,
@@ -44,7 +48,7 @@
         )
         context.orCreateTestableResources.addOverride(
             R.string.config_frontBuiltInDisplayCutoutProtection,
-            "M 0,0 H 10,10 V 10,10 H 0,10 Z"
+            bounds.asPath(),
         )
         context.orCreateTestableResources.addOverride(
             R.string.config_protectedScreenUniqueId,
@@ -52,7 +56,10 @@
         )
     }
 
-    fun addInnerCameraProtection(displayUniqueId: String = "222") {
+    fun addInnerCameraProtection(
+        displayUniqueId: String = "222",
+        bounds: Rect = Rect(/* left = */ 0, /* top = */ 0, /* right = */ 20, /* bottom = */ 20)
+    ) {
         context.orCreateTestableResources.addOverride(R.string.config_protectedInnerCameraId, "2")
         context.orCreateTestableResources.addOverride(
             R.string.config_protectedInnerPhysicalCameraId,
@@ -60,11 +67,13 @@
         )
         context.orCreateTestableResources.addOverride(
             R.string.config_innerBuiltInDisplayCutoutProtection,
-            "M 0,0 H 20,20 V 20,20 H 0,20 Z"
+            bounds.asPath(),
         )
         context.orCreateTestableResources.addOverride(
             R.string.config_protectedInnerScreenUniqueId,
             displayUniqueId
         )
     }
+
+    private fun Rect.asPath() = "M $left, $top H $right V $bottom H $left Z"
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt
index f37c4ae..61c7e1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt
@@ -16,11 +16,16 @@
 
 package com.android.systemui
 
+import android.graphics.Rect
 import android.view.Display
 import android.view.DisplayAdjustments
 import android.view.DisplayCutout
+import android.view.DisplayInfo
+import android.view.Surface
+import android.view.Surface.Rotation
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -39,7 +44,7 @@
         val noCutoutDisplayContext = context.createDisplayContext(noCutoutDisplay)
         val provider = SysUICutoutProvider(noCutoutDisplayContext, fakeProtectionLoader)
 
-        val sysUICutout = provider.cutoutInfoForCurrentDisplay()
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()
 
         assertThat(sysUICutout).isNull()
     }
@@ -50,7 +55,7 @@
         val cutoutDisplayContext = context.createDisplayContext(cutoutDisplay)
         val provider = SysUICutoutProvider(cutoutDisplayContext, fakeProtectionLoader)
 
-        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
 
         assertThat(sysUICutout.cutout).isEqualTo(cutoutDisplay.cutout)
     }
@@ -61,7 +66,7 @@
         val cutoutDisplayContext = context.createDisplayContext(cutoutDisplay)
         val provider = SysUICutoutProvider(cutoutDisplayContext, fakeProtectionLoader)
 
-        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
 
         assertThat(sysUICutout.cameraProtection).isNull()
     }
@@ -72,7 +77,7 @@
         val outerDisplayContext = context.createDisplayContext(OUTER_DISPLAY)
         val provider = SysUICutoutProvider(outerDisplayContext, fakeProtectionLoader)
 
-        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
 
         assertThat(sysUICutout.cameraProtection).isNotNull()
     }
@@ -83,7 +88,7 @@
         val outerDisplayContext = context.createDisplayContext(OUTER_DISPLAY)
         val provider = SysUICutoutProvider(outerDisplayContext, fakeProtectionLoader)
 
-        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
 
         assertThat(sysUICutout.cameraProtection).isNull()
     }
@@ -94,7 +99,7 @@
         val displayContext = context.createDisplayContext(createDisplay(uniqueId = null))
         val provider = SysUICutoutProvider(displayContext, fakeProtectionLoader)
 
-        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
 
         assertThat(sysUICutout.cameraProtection).isNull()
     }
@@ -105,20 +110,170 @@
         val displayContext = context.createDisplayContext(createDisplay(uniqueId = ""))
         val provider = SysUICutoutProvider(displayContext, fakeProtectionLoader)
 
-        val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
 
         assertThat(sysUICutout.cameraProtection).isNull()
     }
 
+    @Test
+    fun cutoutInfo_rotation0_returnsOriginalProtectionBounds() {
+        val provider =
+            setUpProviderWithCameraProtection(
+                displayWidth = 500,
+                displayHeight = 1000,
+                rotation = Surface.ROTATION_0,
+                protectionBounds =
+                    Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+            )
+
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
+
+        assertThat(sysUICutout.cameraProtection!!.bounds)
+            .isEqualTo(
+                Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+            )
+    }
+
+    @Test
+    fun cutoutInfo_rotation90_returnsRotatedProtectionBounds() {
+        val provider =
+            setUpProviderWithCameraProtection(
+                displayWidth = 500,
+                displayHeight = 1000,
+                rotation = Surface.ROTATION_90,
+                protectionBounds =
+                    Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+            )
+
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
+
+        assertThat(sysUICutout.cameraProtection!!.bounds)
+            .isEqualTo(Rect(/* left = */ 10, /* top = */ 10, /* right = */ 110, /* bottom = */ 60))
+    }
+
+    @Test
+    fun cutoutInfo_withRotation_doesNotMutateOriginalBounds() {
+        val displayNaturalWidth = 500
+        val displayNaturalHeight = 1000
+        val originalProtectionBounds =
+            Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+        // Safe copy as we don't know at which layer the mutation could happen
+        val originalProtectionBoundsCopy = Rect(originalProtectionBounds)
+        val display =
+            createDisplay(
+                uniqueId = OUTER_DISPLAY_UNIQUE_ID,
+                rotation = Surface.ROTATION_180,
+                width = displayNaturalWidth,
+                height = displayNaturalHeight,
+            )
+        fakeProtectionLoader.addOuterCameraProtection(
+            displayUniqueId = OUTER_DISPLAY_UNIQUE_ID,
+            bounds = originalProtectionBounds
+        )
+        val provider =
+            SysUICutoutProvider(context.createDisplayContext(display), fakeProtectionLoader)
+
+        // Here we get the rotated bounds once
+        provider.cutoutInfoForCurrentDisplayAndRotation()
+
+        // Rotate display back to original rotation
+        whenever(display.rotation).thenReturn(Surface.ROTATION_0)
+
+        // Now the bounds should match the original ones. We are checking for mutation since Rect
+        // is mutable and has many methods that mutate the instance, and it is easy to do it by
+        // mistake.
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
+        assertThat(sysUICutout.cameraProtection!!.bounds).isEqualTo(originalProtectionBoundsCopy)
+    }
+
+    @Test
+    fun cutoutInfo_rotation180_returnsRotatedProtectionBounds() {
+        val provider =
+            setUpProviderWithCameraProtection(
+                displayWidth = 500,
+                displayHeight = 1000,
+                rotation = Surface.ROTATION_180,
+                protectionBounds =
+                    Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+            )
+
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
+
+        assertThat(sysUICutout.cameraProtection!!.bounds)
+            .isEqualTo(Rect(/* left = */ 10, /* top = */ 890, /* right = */ 60, /* bottom = */ 990))
+    }
+
+    @Test
+    fun cutoutInfo_rotation270_returnsRotatedProtectionBounds() {
+        val provider =
+            setUpProviderWithCameraProtection(
+                displayWidth = 500,
+                displayHeight = 1000,
+                rotation = Surface.ROTATION_270,
+                protectionBounds =
+                    Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+            )
+
+        val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
+
+        assertThat(sysUICutout.cameraProtection!!.bounds)
+            .isEqualTo(
+                Rect(/* left = */ 890, /* top = */ 440, /* right = */ 990, /* bottom = */ 490)
+            )
+    }
+
+    private fun setUpProviderWithCameraProtection(
+        displayWidth: Int,
+        displayHeight: Int,
+        rotation: Int = Surface.ROTATION_0,
+        protectionBounds: Rect,
+    ): SysUICutoutProvider {
+        val display =
+            createDisplay(
+                uniqueId = OUTER_DISPLAY_UNIQUE_ID,
+                rotation = rotation,
+                width =
+                    if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
+                        displayWidth
+                    } else {
+                        displayHeight
+                    },
+                height =
+                    if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180)
+                        displayHeight
+                    else displayWidth,
+            )
+        fakeProtectionLoader.addOuterCameraProtection(
+            displayUniqueId = OUTER_DISPLAY_UNIQUE_ID,
+            bounds = protectionBounds
+        )
+        return SysUICutoutProvider(context.createDisplayContext(display), fakeProtectionLoader)
+    }
+
     companion object {
         private const val OUTER_DISPLAY_UNIQUE_ID = "outer"
         private val OUTER_DISPLAY = createDisplay(uniqueId = OUTER_DISPLAY_UNIQUE_ID)
 
         private fun createDisplay(
+            width: Int = 500,
+            height: Int = 1000,
+            @Rotation rotation: Int = Surface.ROTATION_0,
             uniqueId: String? = "uniqueId",
             cutout: DisplayCutout? = mock<DisplayCutout>()
         ) =
             mock<Display> {
+                whenever(this.getDisplayInfo(any())).thenAnswer {
+                    val displayInfo = it.arguments[0] as DisplayInfo
+                    displayInfo.rotation = rotation
+                    displayInfo.logicalWidth = width
+                    displayInfo.logicalHeight = height
+                    return@thenAnswer true
+                }
+                // Setting width and height to smaller values to simulate real behavior of this API
+                // not always returning the real display size
+                whenever(this.width).thenReturn(width - 5)
+                whenever(this.height).thenReturn(height - 10)
+                whenever(this.rotation).thenReturn(rotation)
                 whenever(this.displayAdjustments).thenReturn(DisplayAdjustments())
                 whenever(this.cutout).thenReturn(cutout)
                 whenever(this.uniqueId).thenReturn(uniqueId)