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)