Take camera protection into account in status bar content insets
+ Introduces SysUICutoutInformation, which bundles display cutout and
camera protection info.
To be able to associate a camera protection with a specific display
cutout, we have to add the unique display id that is associated with
the camera protection info, and then match it with the display of the
cutout.
Change-Id: Ic5336d17dfafae981ca5f12231941fa3bf77b006
Test: StatusBarContentInsetsProviderTest.kt
Flag: NONE
Bug: 321955113
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
similarity index 62%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index 84b2c4b..53b262b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -21,7 +21,11 @@
import android.graphics.Rect
import android.view.Display
import android.view.DisplayCutout
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.CameraProtectionInfo
+import com.android.systemui.SysUICutoutInformation
+import com.android.systemui.SysUICutoutProvider
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -38,30 +42,30 @@
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
-import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
+@RunWith(AndroidJUnit4::class)
@SmallTest
class StatusBarContentInsetsProviderTest : SysuiTestCase() {
- @Mock private lateinit var dc: DisplayCutout
- @Mock private lateinit var contextMock: Context
- @Mock private lateinit var display: Display
- private lateinit var configurationController: ConfigurationController
-
+ private val sysUICutout = mock<SysUICutoutInformation>()
+ private val dc = mock<DisplayCutout>()
+ private val contextMock = mock<Context>()
+ private val display = mock<Display>()
private val configuration = Configuration()
+ private lateinit var configurationController: ConfigurationController
+
@Before
fun setup() {
- MockitoAnnotations.initMocks(this)
- `when`(contextMock.display).thenReturn(display)
+ whenever(sysUICutout.cutout).thenReturn(dc)
+ whenever(contextMock.display).thenReturn(display)
context.ensureTestableResources()
- `when`(contextMock.resources).thenReturn(context.resources)
- `when`(contextMock.resources.configuration).thenReturn(configuration)
- `when`(contextMock.createConfigurationContext(any())).thenAnswer {
+ whenever(contextMock.resources).thenReturn(context.resources)
+ whenever(contextMock.resources.configuration).thenReturn(configuration)
+ whenever(contextMock.createConfigurationContext(any())).thenAnswer {
context.createConfigurationContext(it.arguments[0] as Configuration)
}
configurationController = ConfigurationControllerImpl(contextMock)
@@ -117,7 +121,7 @@
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
screenBounds,
sbHeightLandscape,
minLeftPadding,
@@ -161,7 +165,7 @@
}
@Test
- fun testCalculateInsetsForRotationWithRotatedResources_topLeftCutout() {
+ fun testCalculateInsetsForRotationWithRotatedResources_topLeftCutout_noCameraProtection() {
// GIVEN a device in portrait mode with width < height and a display cutout in the top-left
val screenBounds = Rect(0, 0, 1080, 2160)
val dcBounds = Rect(0, 0, 100, 100)
@@ -174,7 +178,7 @@
val dotWidth = 10
val statusBarContentHeight = 15
- `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
// THEN rotations which share a short side should use the greater value between rounded
// corner padding and the display cutout's size
@@ -187,7 +191,7 @@
var bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
screenBounds,
sbHeightPortrait,
minLeftPadding,
@@ -208,7 +212,7 @@
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
screenBounds,
sbHeightLandscape,
minLeftPadding,
@@ -231,7 +235,7 @@
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
screenBounds,
sbHeightPortrait,
minLeftPadding,
@@ -253,7 +257,335 @@
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+ }
+
+ @Test
+ fun testCalculateInsetsForRotationWithRotatedResources_topLeftCutout_withCameraProtection() {
+ // GIVEN a device in portrait mode with width < height and a display cutout in the top-left
+ val screenBounds = Rect(0, 0, 1080, 2160)
+ val dcBounds = Rect(0, 0, 100, 100)
+ val protectionBounds = Rect(10, 10, 110, 110)
+ val minLeftPadding = 20
+ val minRightPadding = 20
+ val sbHeightPortrait = 100
+ val sbHeightLandscape = 60
+ val currentRotation = ROTATION_NONE
+ val isRtl = false
+ val dotWidth = 10
+ val statusBarContentHeight = 15
+
+ val protectionInfo = mock<CameraProtectionInfo> {
+ whenever(this.cutoutBounds).thenReturn(protectionBounds)
+ }
+ whenever(sysUICutout.cameraProtection).thenReturn(protectionInfo)
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
+
+ // THEN rotations which share a short side should use the greater value between rounded
+ // corner padding, the display cutout's size, and the camera protections' size.
+ var targetRotation = ROTATION_NONE
+ var expectedBounds = Rect(protectionBounds.right,
+ 0,
+ screenBounds.right - minRightPadding,
+ sbHeightPortrait)
+
+ var bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ targetRotation = ROTATION_LANDSCAPE
+ expectedBounds = Rect(protectionBounds.bottom,
+ 0,
+ screenBounds.height() - minRightPadding,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // THEN the side that does NOT share a short side with the display cutout ignores the
+ // display cutout bounds
+ targetRotation = ROTATION_UPSIDE_DOWN
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.width() - minRightPadding,
+ sbHeightPortrait)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // Phone in portrait, seascape (rot_270) bounds
+ targetRotation = ROTATION_SEASCAPE
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.height() - protectionBounds.bottom - dotWidth,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+ }
+
+ @Test
+ fun testCalculateInsetsForRotationWithRotatedResources_topRightCutout_noCameraProtection() {
+ // GIVEN a device in portrait mode with width < height and a display cutout in the top-left
+ val screenBounds = Rect(0, 0, 1000, 2000)
+ val dcBounds = Rect(900, 0, 1000, 100)
+ val minLeftPadding = 20
+ val minRightPadding = 20
+ val sbHeightPortrait = 100
+ val sbHeightLandscape = 60
+ val currentRotation = ROTATION_NONE
+ val isRtl = false
+ val dotWidth = 10
+ val statusBarContentHeight = 15
+
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
+
+ // THEN rotations which share a short side should use the greater value between rounded
+ // corner padding and the display cutout's size
+ var targetRotation = ROTATION_NONE
+ var expectedBounds = Rect(minLeftPadding,
+ 0,
+ dcBounds.left - dotWidth,
+ sbHeightPortrait)
+
+ var bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ targetRotation = ROTATION_LANDSCAPE
+ expectedBounds = Rect(dcBounds.height(),
+ 0,
+ screenBounds.height() - minRightPadding,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // THEN the side that does NOT share a short side with the display cutout ignores the
+ // display cutout bounds
+ targetRotation = ROTATION_UPSIDE_DOWN
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.width() - minRightPadding,
+ sbHeightPortrait)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // Phone in portrait, seascape (rot_270) bounds
+ targetRotation = ROTATION_SEASCAPE
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.height() - dcBounds.height() - dotWidth,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+ }
+
+ @Test
+ fun testCalculateInsetsForRotationWithRotatedResources_topRightCutout_withCameraProtection() {
+ // GIVEN a device in portrait mode with width < height and a display cutout in the top-left
+ val screenBounds = Rect(0, 0, 1000, 2000)
+ val dcBounds = Rect(900, 0, 1000, 100)
+ val protectionBounds = Rect(890, 10, 990, 110)
+ val minLeftPadding = 20
+ val minRightPadding = 20
+ val sbHeightPortrait = 100
+ val sbHeightLandscape = 60
+ val currentRotation = ROTATION_NONE
+ val isRtl = false
+ val dotWidth = 10
+ val statusBarContentHeight = 15
+
+ val protectionInfo = mock<CameraProtectionInfo> {
+ whenever(this.cutoutBounds).thenReturn(protectionBounds)
+ }
+ whenever(sysUICutout.cameraProtection).thenReturn(protectionInfo)
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
+
+ // THEN rotations which share a short side should use the greater value between rounded
+ // corner padding, the display cutout's size, and the camera protections' size.
+ var targetRotation = ROTATION_NONE
+ var expectedBounds = Rect(minLeftPadding,
+ 0,
+ protectionBounds.left - dotWidth,
+ sbHeightPortrait)
+
+ var bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ targetRotation = ROTATION_LANDSCAPE
+ expectedBounds = Rect(protectionBounds.bottom,
+ 0,
+ screenBounds.height() - minRightPadding,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // THEN the side that does NOT share a short side with the display cutout ignores the
+ // display cutout bounds
+ targetRotation = ROTATION_UPSIDE_DOWN
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.width() - minRightPadding,
+ sbHeightPortrait)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // Phone in portrait, seascape (rot_270) bounds
+ targetRotation = ROTATION_SEASCAPE
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.height() - protectionBounds.bottom - dotWidth,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
screenBounds,
sbHeightLandscape,
minLeftPadding,
@@ -273,7 +605,7 @@
val bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation = ROTATION_NONE,
targetRotation = ROTATION_NONE,
- displayCutout = dc,
+ sysUICutout = sysUICutout,
maxBounds = Rect(0, 0, 1080, 2160),
statusBarHeight = 100,
minLeft = 0,
@@ -293,7 +625,7 @@
val bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation = ROTATION_NONE,
targetRotation = ROTATION_NONE,
- displayCutout = dc,
+ sysUICutout = sysUICutout,
maxBounds = Rect(0, 0, 1080, 2160),
statusBarHeight = 100,
minLeft = 0,
@@ -321,6 +653,7 @@
val screenBounds = Rect(0, 0, 1080, 2160)
// cutout centered at the top
val dcBounds = Rect(490, 0, 590, 100)
+ val protectionBounds = Rect(480, 10, 600, 90)
val minLeftPadding = 20
val minRightPadding = 20
val sbHeightPortrait = 100
@@ -330,7 +663,11 @@
val dotWidth = 10
val statusBarContentHeight = 15
- `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
+ val protectionInfo = mock<CameraProtectionInfo> {
+ whenever(this.cutoutBounds).thenReturn(protectionBounds)
+ }
+ whenever(sysUICutout.cameraProtection).thenReturn(protectionInfo)
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
// THEN only the landscape/seascape rotations should avoid the cutout area because of the
// potential letterboxing
@@ -343,7 +680,7 @@
var bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout = sysUICutout,
screenBounds,
sbHeightPortrait,
minLeftPadding,
@@ -364,7 +701,7 @@
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout = sysUICutout,
screenBounds,
sbHeightLandscape,
minLeftPadding,
@@ -385,7 +722,7 @@
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout = sysUICutout,
screenBounds,
sbHeightPortrait,
minLeftPadding,
@@ -406,7 +743,7 @@
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout = sysUICutout,
screenBounds,
sbHeightLandscape,
minLeftPadding,
@@ -528,7 +865,7 @@
val dotWidth = 10
val statusBarContentHeight = 15
- `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
// THEN left should be set to the display cutout width, and right should use the minRight
val targetRotation = ROTATION_NONE
@@ -540,7 +877,7 @@
val bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
screenBounds,
sbHeightPortrait,
minLeftPadding,
@@ -557,7 +894,7 @@
fun testDisplayChanged_returnsUpdatedInsets() {
// GIVEN: get insets on the first display and switch to the second display
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
- mock<DumpManager>(), mock<CommandRegistry>())
+ mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
val firstDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
@@ -576,7 +913,7 @@
// GIVEN: get insets on the first display, switch to the second display,
// get insets and switch back
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
- mock<DumpManager>(), mock<CommandRegistry>())
+ mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
val firstDisplayInsetsFirstCall = provider
@@ -602,7 +939,7 @@
configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100)
configurationController.onConfigurationChanged(configuration)
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
- mock<DumpManager>(), mock<CommandRegistry>())
+ mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
val listener = object : StatusBarContentInsetsChangedListener {
var triggered = false
@@ -624,7 +961,7 @@
fun onDensityOrFontScaleChanged_listenerNotified() {
configuration.densityDpi = 12
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
- mock<DumpManager>(), mock<CommandRegistry>())
+ mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
val listener = object : StatusBarContentInsetsChangedListener {
var triggered = false
@@ -645,7 +982,7 @@
@Test
fun onThemeChanged_listenerNotified() {
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
- mock<DumpManager>(), mock<CommandRegistry>())
+ mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
val listener = object : StatusBarContentInsetsChangedListener {
var triggered = false
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 7a83070..65c69f7 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -542,6 +542,9 @@
<string translatable="false" name="config_protectedCameraId"></string>
<!-- Physical ID for the camera of outer display that needs extra protection -->
<string translatable="false" name="config_protectedPhysicalCameraId"></string>
+ <!-- Unique ID of the outer display that contains the camera that needs protection. -->
+ <string translatable="false" name="config_protectedScreenUniqueId"></string>
+
<!-- Similar to config_frontBuiltInDisplayCutoutProtection but for inner display. -->
<string translatable="false" name="config_innerBuiltInDisplayCutoutProtection"></string>
@@ -550,6 +553,8 @@
<string translatable="false" name="config_protectedInnerCameraId"></string>
<!-- Physical ID for the camera of inner display that needs extra protection -->
<string translatable="false" name="config_protectedInnerPhysicalCameraId"></string>
+ <!-- Unique ID of the inner display that contains the camera that needs protection. -->
+ <string translatable="false" name="config_protectedInnerScreenUniqueId"></string>
<!-- Comma-separated list of packages to exclude from camera protection e.g.
"com.android.systemui,com.android.xyz" -->
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
index bbab4de..6314bd9 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
@@ -24,4 +24,5 @@
val physicalCameraId: String?,
val cutoutProtectionPath: Path,
val cutoutBounds: Rect,
+ val displayUniqueId: String?,
)
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt
index 8fe9389..6cee28b 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt
@@ -25,15 +25,21 @@
import javax.inject.Inject
import kotlin.math.roundToInt
-class CameraProtectionLoader @Inject constructor(private val context: Context) {
+interface CameraProtectionLoader {
+ fun loadCameraProtectionInfoList(): List<CameraProtectionInfo>
+}
- fun loadCameraProtectionInfoList(): List<CameraProtectionInfo> {
+class CameraProtectionLoaderImpl @Inject constructor(private val context: Context) :
+ CameraProtectionLoader {
+
+ override fun loadCameraProtectionInfoList(): List<CameraProtectionInfo> {
val list = mutableListOf<CameraProtectionInfo>()
val front =
loadCameraProtectionInfo(
R.string.config_protectedCameraId,
R.string.config_protectedPhysicalCameraId,
- R.string.config_frontBuiltInDisplayCutoutProtection
+ R.string.config_frontBuiltInDisplayCutoutProtection,
+ R.string.config_protectedScreenUniqueId,
)
if (front != null) {
list.add(front)
@@ -42,7 +48,8 @@
loadCameraProtectionInfo(
R.string.config_protectedInnerCameraId,
R.string.config_protectedInnerPhysicalCameraId,
- R.string.config_innerBuiltInDisplayCutoutProtection
+ R.string.config_innerBuiltInDisplayCutoutProtection,
+ R.string.config_protectedInnerScreenUniqueId,
)
if (inner != null) {
list.add(inner)
@@ -53,7 +60,8 @@
private fun loadCameraProtectionInfo(
cameraIdRes: Int,
physicalCameraIdRes: Int,
- pathRes: Int
+ pathRes: Int,
+ displayUniqueIdRes: Int,
): CameraProtectionInfo? {
val logicalCameraId = context.getString(cameraIdRes)
if (logicalCameraId.isNullOrEmpty()) {
@@ -70,11 +78,13 @@
computed.right.roundToInt(),
computed.bottom.roundToInt()
)
+ val displayUniqueId = context.getString(displayUniqueIdRes)
return CameraProtectionInfo(
logicalCameraId,
physicalCameraId,
protectionPath,
- protectionBounds
+ protectionBounds,
+ displayUniqueId
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionModule.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionModule.kt
new file mode 100644
index 0000000..58680a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionModule.kt
@@ -0,0 +1,26 @@
+/*
+ * 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
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface CameraProtectionModule {
+
+ @Binds fun cameraProtectionLoaderImpl(impl: CameraProtectionLoaderImpl): CameraProtectionLoader
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SysUICutoutInformation.kt b/packages/SystemUI/src/com/android/systemui/SysUICutoutInformation.kt
new file mode 100644
index 0000000..fc0b97e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/SysUICutoutInformation.kt
@@ -0,0 +1,24 @@
+/*
+ * 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
+
+import android.view.DisplayCutout
+
+data class SysUICutoutInformation(
+ val cutout: DisplayCutout,
+ val cameraProtection: CameraProtectionInfo?
+)
diff --git a/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt b/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt
new file mode 100644
index 0000000..aad9341
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.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
+
+import android.content.Context
+import android.view.DisplayCutout
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class SysUICutoutProvider
+@Inject
+constructor(
+ private val context: Context,
+ private val cameraProtectionLoader: CameraProtectionLoader,
+) {
+
+ private val cameraProtectionList by lazy {
+ cameraProtectionLoader.loadCameraProtectionInfoList()
+ }
+
+ fun cutoutInfoForCurrentDisplay(): SysUICutoutInformation? {
+ val display = context.display
+ val displayCutout: DisplayCutout = display.cutout ?: return null
+ val displayUniqueId: String? = display.uniqueId
+ if (displayUniqueId.isNullOrEmpty()) {
+ return SysUICutoutInformation(displayCutout, cameraProtection = null)
+ }
+ val cameraProtection: CameraProtectionInfo? =
+ cameraProtectionList.firstOrNull { it.displayUniqueId == displayUniqueId }
+ return SysUICutoutInformation(displayCutout, cameraProtection)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index efcbd47..28fd9a9 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -27,6 +27,7 @@
import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.BootCompleteCache;
import com.android.systemui.BootCompleteCacheImpl;
+import com.android.systemui.CameraProtectionModule;
import com.android.systemui.accessibility.AccessibilityModule;
import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
import com.android.systemui.appops.dagger.AppOpsModule;
@@ -177,6 +178,7 @@
BouncerInteractorModule.class,
BouncerRepositoryModule.class,
BouncerViewModule.class,
+ CameraProtectionModule.class,
ClipboardOverlayModule.class,
ClockRegistryModule.class,
CommunalModule.class,
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 877bd7c..e84b7a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -44,6 +44,8 @@
import com.android.app.tracing.traceSection
import com.android.systemui.BottomMarginCommand
import com.android.systemui.StatusBarInsetsCommand
+import com.android.systemui.SysUICutoutInformation
+import com.android.systemui.SysUICutoutProvider
import com.android.systemui.statusbar.commandline.CommandRegistry
import java.io.PrintWriter
import java.lang.Math.max
@@ -69,6 +71,7 @@
val configurationController: ConfigurationController,
val dumpManager: DumpManager,
val commandRegistry: CommandRegistry,
+ val sysUICutoutProvider: SysUICutoutProvider,
) : CallbackController<StatusBarContentInsetsChangedListener>,
ConfigurationController.ConfigurationListener,
Dumpable {
@@ -176,7 +179,8 @@
*/
fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Insets =
traceSection(tag = "StatusBarContentInsetsProvider.getStatusBarContentInsetsForRotation") {
- val displayCutout = checkNotNull(context.display).cutout
+ val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplay()
+ val displayCutout = sysUICutout?.cutout
val key = getCacheKey(rotation, displayCutout)
val screenBounds = context.resources.configuration.windowConfiguration.maxBounds
@@ -187,7 +191,7 @@
val width = point.logicalWidth(rotation)
val area = insetsCache[key] ?: getAndSetCalculatedAreaForRotation(
- rotation, displayCutout, getResourcesForRotation(rotation, context), key)
+ rotation, sysUICutout, getResourcesForRotation(rotation, context), key)
Insets.of(area.left, area.top, /* right= */ width - area.right, /* bottom= */ 0)
}
@@ -212,10 +216,11 @@
fun getStatusBarContentAreaForRotation(
@Rotation rotation: Int
): Rect {
- val displayCutout = checkNotNull(context.display).cutout
+ val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplay()
+ val displayCutout = sysUICutout?.cutout
val key = getCacheKey(rotation, displayCutout)
return insetsCache[key] ?: getAndSetCalculatedAreaForRotation(
- rotation, displayCutout, getResourcesForRotation(rotation, context), key)
+ rotation, sysUICutout, getResourcesForRotation(rotation, context), key)
}
/**
@@ -228,18 +233,18 @@
private fun getAndSetCalculatedAreaForRotation(
@Rotation targetRotation: Int,
- displayCutout: DisplayCutout?,
+ sysUICutout: SysUICutoutInformation?,
rotatedResources: Resources,
key: CacheKey
): Rect {
- return getCalculatedAreaForRotation(displayCutout, targetRotation, rotatedResources)
+ return getCalculatedAreaForRotation(sysUICutout, targetRotation, rotatedResources)
.also {
insetsCache.put(key, it)
}
}
private fun getCalculatedAreaForRotation(
- displayCutout: DisplayCutout?,
+ sysUICutout: SysUICutoutInformation?,
@Rotation targetRotation: Int,
rotatedResources: Resources
): Rect {
@@ -271,7 +276,7 @@
return calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- displayCutout,
+ sysUICutout,
context.resources.configuration.windowConfiguration.maxBounds,
SystemBarUtils.getStatusBarHeightForRotation(context, targetRotation),
minLeft,
@@ -415,7 +420,7 @@
fun calculateInsetsForRotationWithRotatedResources(
@Rotation currentRotation: Int,
@Rotation targetRotation: Int,
- displayCutout: DisplayCutout?,
+ sysUICutout: SysUICutoutInformation?,
maxBounds: Rect,
statusBarHeight: Int,
minLeft: Int,
@@ -434,7 +439,7 @@
val rotZeroBounds = getRotationZeroDisplayBounds(maxBounds, currentRotation)
return getStatusBarContentBounds(
- displayCutout,
+ sysUICutout,
statusBarHeight,
rotZeroBounds.right,
rotZeroBounds.bottom,
@@ -470,7 +475,7 @@
* rotation
*/
private fun getStatusBarContentBounds(
- displayCutout: DisplayCutout?,
+ sysUICutout: SysUICutoutInformation?,
sbHeight: Int,
width: Int,
height: Int,
@@ -489,19 +494,17 @@
val logicalDisplayWidth = if (targetRotation.isHorizontal()) height else width
- val cutoutRects = displayCutout?.boundingRects
- if (cutoutRects == null || cutoutRects.isEmpty()) {
- return Rect(minLeft,
- insetTop,
- logicalDisplayWidth - minRight,
- sbHeight)
+ val cutoutRects = sysUICutout?.cutout?.boundingRects
+ if (cutoutRects.isNullOrEmpty()) {
+ return Rect(minLeft, insetTop, logicalDisplayWidth - minRight, sbHeight)
}
- val relativeRotation = if (currentRotation - targetRotation < 0) {
- currentRotation - targetRotation + 4
- } else {
- currentRotation - targetRotation
- }
+ val relativeRotation =
+ if (currentRotation - targetRotation < 0) {
+ currentRotation - targetRotation + 4
+ } else {
+ currentRotation - targetRotation
+ }
// Size of the status bar window for the given rotation relative to our exact rotation
val sbRect = sbRect(relativeRotation, sbHeight, Pair(cWidth, cHeight))
@@ -509,19 +512,26 @@
var leftMargin = minLeft
var rightMargin = minRight
for (cutoutRect in cutoutRects) {
+ val protectionRect = sysUICutout.cameraProtection?.cutoutBounds
+ val actualCutoutRect =
+ if (protectionRect?.intersects(cutoutRect) == true) {
+ rectUnion(cutoutRect, protectionRect)
+ } else {
+ cutoutRect
+ }
// There is at most one non-functional area per short edge of the device. So if the status
// bar doesn't share a short edge with the cutout, we can ignore its insets because there
// will be no letter-boxing to worry about
- if (!shareShortEdge(sbRect, cutoutRect, cWidth, cHeight)) {
+ if (!shareShortEdge(sbRect, actualCutoutRect, cWidth, cHeight)) {
continue
}
- if (cutoutRect.touchesLeftEdge(relativeRotation, cWidth, cHeight)) {
- var logicalWidth = cutoutRect.logicalWidth(relativeRotation)
+ if (actualCutoutRect.touchesLeftEdge(relativeRotation, cWidth, cHeight)) {
+ var logicalWidth = actualCutoutRect.logicalWidth(relativeRotation)
if (isRtl) logicalWidth += dotWidth
leftMargin = max(logicalWidth, leftMargin)
- } else if (cutoutRect.touchesRightEdge(relativeRotation, cWidth, cHeight)) {
- var logicalWidth = cutoutRect.logicalWidth(relativeRotation)
+ } else if (actualCutoutRect.touchesRightEdge(relativeRotation, cWidth, cHeight)) {
+ var logicalWidth = actualCutoutRect.logicalWidth(relativeRotation)
if (!isRtl) logicalWidth += dotWidth
rightMargin = max(rightMargin, logicalWidth)
}
@@ -532,6 +542,11 @@
return Rect(leftMargin, insetTop, logicalDisplayWidth - rightMargin, sbHeight)
}
+private fun rectUnion(first: Rect, second: Rect) = Rect(first).apply { union(second) }
+
+private fun Rect.intersects(other: Rect): Boolean =
+ intersects(other.left, other.top, other.right, other.bottom)
+
/*
* Returns the inset top of the status bar.
*
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
index 64cd526..f776a63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
@@ -347,8 +347,8 @@
return CameraAvailabilityListener.build(
context,
context.mainExecutor,
- CameraProtectionLoader((context))
- )
+ CameraProtectionLoaderImpl((context))
+ )
.also {
it.addTransitionCallback(cameraTransitionCallback)
it.startListening()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
similarity index 75%
rename from packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
index 238e5e9..a19a0c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
@@ -27,9 +27,9 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
-class CameraProtectionLoaderTest : SysuiTestCase() {
+class CameraProtectionLoaderImplTest : SysuiTestCase() {
- private val loader = CameraProtectionLoader(context)
+ private val loader = CameraProtectionLoaderImpl(context)
@Before
fun setUp() {
@@ -39,19 +39,21 @@
R.string.config_frontBuiltInDisplayCutoutProtection,
OUTER_CAMERA_PROTECTION_PATH
)
+ overrideResource(R.string.config_protectedScreenUniqueId, OUTER_SCREEN_UNIQUE_ID)
overrideResource(R.string.config_protectedInnerCameraId, INNER_CAMERA_LOGICAL_ID)
overrideResource(R.string.config_protectedInnerPhysicalCameraId, INNER_CAMERA_PHYSICAL_ID)
overrideResource(
R.string.config_innerBuiltInDisplayCutoutProtection,
INNER_CAMERA_PROTECTION_PATH
)
+ overrideResource(R.string.config_protectedInnerScreenUniqueId, INNER_SCREEN_UNIQUE_ID)
}
@Test
fun loadCameraProtectionInfoList() {
- val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+ val protectionList = loadProtectionList()
- assertThat(protectionInfos)
+ assertThat(protectionList)
.containsExactly(OUTER_CAMERA_PROTECTION_INFO, INNER_CAMERA_PROTECTION_INFO)
}
@@ -59,18 +61,18 @@
fun loadCameraProtectionInfoList_outerCameraIdEmpty_onlyReturnsInnerInfo() {
overrideResource(R.string.config_protectedCameraId, "")
- val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+ val protectionList = loadProtectionList()
- assertThat(protectionInfos).containsExactly(INNER_CAMERA_PROTECTION_INFO)
+ assertThat(protectionList).containsExactly(INNER_CAMERA_PROTECTION_INFO)
}
@Test
fun loadCameraProtectionInfoList_innerCameraIdEmpty_onlyReturnsOuterInfo() {
overrideResource(R.string.config_protectedInnerCameraId, "")
- val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+ val protectionList = loadProtectionList()
- assertThat(protectionInfos).containsExactly(OUTER_CAMERA_PROTECTION_INFO)
+ assertThat(protectionList).containsExactly(OUTER_CAMERA_PROTECTION_INFO)
}
@Test
@@ -78,13 +80,16 @@
overrideResource(R.string.config_protectedCameraId, "")
overrideResource(R.string.config_protectedInnerCameraId, "")
- val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+ val protectionList = loadProtectionList()
- assertThat(protectionInfos).isEmpty()
+ assertThat(protectionList).isEmpty()
}
+ private fun loadProtectionList() =
+ loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+
private fun CameraProtectionInfo.toTestableVersion() =
- TestableProtectionInfo(logicalCameraId, physicalCameraId, cutoutBounds)
+ TestableProtectionInfo(logicalCameraId, physicalCameraId, cutoutBounds, displayUniqueId)
/**
* "Testable" version, because the original version contains a Path property, which doesn't
@@ -94,6 +99,7 @@
val logicalCameraId: String,
val physicalCameraId: String?,
val cutoutBounds: Rect,
+ val displayUniqueId: String?,
)
companion object {
@@ -102,11 +108,13 @@
private const val OUTER_CAMERA_PROTECTION_PATH = "M 0,0 H 10,10 V 10,10 H 0,10 Z"
private val OUTER_CAMERA_PROTECTION_BOUNDS =
Rect(/* left = */ 0, /* top = */ 0, /* right = */ 10, /* bottom = */ 10)
+ private const val OUTER_SCREEN_UNIQUE_ID = "111"
private val OUTER_CAMERA_PROTECTION_INFO =
TestableProtectionInfo(
OUTER_CAMERA_LOGICAL_ID,
OUTER_CAMERA_PHYSICAL_ID,
- OUTER_CAMERA_PROTECTION_BOUNDS
+ OUTER_CAMERA_PROTECTION_BOUNDS,
+ OUTER_SCREEN_UNIQUE_ID,
)
private const val INNER_CAMERA_LOGICAL_ID = "2"
@@ -114,11 +122,13 @@
private const val INNER_CAMERA_PROTECTION_PATH = "M 0,0 H 20,20 V 20,20 H 0,20 Z"
private val INNER_CAMERA_PROTECTION_BOUNDS =
Rect(/* left = */ 0, /* top = */ 0, /* right = */ 20, /* bottom = */ 20)
+ private const val INNER_SCREEN_UNIQUE_ID = "222"
private val INNER_CAMERA_PROTECTION_INFO =
TestableProtectionInfo(
INNER_CAMERA_LOGICAL_ID,
INNER_CAMERA_PHYSICAL_ID,
- INNER_CAMERA_PROTECTION_BOUNDS
+ INNER_CAMERA_PROTECTION_BOUNDS,
+ INNER_SCREEN_UNIQUE_ID,
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt b/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
new file mode 100644
index 0000000..f769b4e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
@@ -0,0 +1,70 @@
+/*
+ * 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
+
+import com.android.systemui.res.R
+
+class FakeCameraProtectionLoader(private val context: SysuiTestableContext) :
+ CameraProtectionLoader {
+
+ private val realLoader = CameraProtectionLoaderImpl(context)
+
+ override fun loadCameraProtectionInfoList(): List<CameraProtectionInfo> =
+ realLoader.loadCameraProtectionInfoList()
+
+ fun clearProtectionInfoList() {
+ context.orCreateTestableResources.addOverride(R.string.config_protectedCameraId, "")
+ context.orCreateTestableResources.addOverride(R.string.config_protectedInnerCameraId, "")
+ }
+
+ fun addAllProtections() {
+ addOuterCameraProtection()
+ addInnerCameraProtection()
+ }
+
+ fun addOuterCameraProtection(displayUniqueId: String = "111") {
+ context.orCreateTestableResources.addOverride(R.string.config_protectedCameraId, "1")
+ context.orCreateTestableResources.addOverride(
+ R.string.config_protectedPhysicalCameraId,
+ "11"
+ )
+ context.orCreateTestableResources.addOverride(
+ R.string.config_frontBuiltInDisplayCutoutProtection,
+ "M 0,0 H 10,10 V 10,10 H 0,10 Z"
+ )
+ context.orCreateTestableResources.addOverride(
+ R.string.config_protectedScreenUniqueId,
+ displayUniqueId
+ )
+ }
+
+ fun addInnerCameraProtection(displayUniqueId: String = "222") {
+ context.orCreateTestableResources.addOverride(R.string.config_protectedInnerCameraId, "2")
+ context.orCreateTestableResources.addOverride(
+ R.string.config_protectedInnerPhysicalCameraId,
+ "22"
+ )
+ context.orCreateTestableResources.addOverride(
+ R.string.config_innerBuiltInDisplayCutoutProtection,
+ "M 0,0 H 20,20 V 20,20 H 0,20 Z"
+ )
+ context.orCreateTestableResources.addOverride(
+ R.string.config_protectedInnerScreenUniqueId,
+ displayUniqueId
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 1f1fa72..c20367e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -177,7 +177,7 @@
new FakeFacePropertyRepository();
private List<DecorProvider> mMockCutoutList;
private final CameraProtectionLoader mCameraProtectionLoader =
- new CameraProtectionLoader(mContext);
+ new CameraProtectionLoaderImpl(mContext);
@Before
public void setup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt
new file mode 100644
index 0000000..f37c4ae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt
@@ -0,0 +1,127 @@
+/*
+ * 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
+
+import android.view.Display
+import android.view.DisplayAdjustments
+import android.view.DisplayCutout
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SysUICutoutProviderTest : SysuiTestCase() {
+
+ private val fakeProtectionLoader = FakeCameraProtectionLoader(context)
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_noCutout_returnsNull() {
+ val noCutoutDisplay = createDisplay(cutout = null)
+ val noCutoutDisplayContext = context.createDisplayContext(noCutoutDisplay)
+ val provider = SysUICutoutProvider(noCutoutDisplayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()
+
+ assertThat(sysUICutout).isNull()
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_returnsCutout() {
+ val cutoutDisplay = createDisplay()
+ val cutoutDisplayContext = context.createDisplayContext(cutoutDisplay)
+ val provider = SysUICutoutProvider(cutoutDisplayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cutout).isEqualTo(cutoutDisplay.cutout)
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_noAssociatedProtection_returnsNoProtection() {
+ val cutoutDisplay = createDisplay()
+ val cutoutDisplayContext = context.createDisplayContext(cutoutDisplay)
+ val provider = SysUICutoutProvider(cutoutDisplayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cameraProtection).isNull()
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_outerDisplay_protectionAssociated_returnsProtection() {
+ fakeProtectionLoader.addOuterCameraProtection(displayUniqueId = OUTER_DISPLAY_UNIQUE_ID)
+ val outerDisplayContext = context.createDisplayContext(OUTER_DISPLAY)
+ val provider = SysUICutoutProvider(outerDisplayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cameraProtection).isNotNull()
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_outerDisplay_protectionNotAvailable_returnsNullProtection() {
+ fakeProtectionLoader.clearProtectionInfoList()
+ val outerDisplayContext = context.createDisplayContext(OUTER_DISPLAY)
+ val provider = SysUICutoutProvider(outerDisplayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cameraProtection).isNull()
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_displayWithNullId_protectionsWithNoId_returnsNullProtection() {
+ fakeProtectionLoader.addOuterCameraProtection(displayUniqueId = "")
+ val displayContext = context.createDisplayContext(createDisplay(uniqueId = null))
+ val provider = SysUICutoutProvider(displayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cameraProtection).isNull()
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_displayWithEmptyId_protectionsWithNoId_returnsNullProtection() {
+ fakeProtectionLoader.addOuterCameraProtection(displayUniqueId = "")
+ val displayContext = context.createDisplayContext(createDisplay(uniqueId = ""))
+ val provider = SysUICutoutProvider(displayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cameraProtection).isNull()
+ }
+
+ companion object {
+ private const val OUTER_DISPLAY_UNIQUE_ID = "outer"
+ private val OUTER_DISPLAY = createDisplay(uniqueId = OUTER_DISPLAY_UNIQUE_ID)
+
+ private fun createDisplay(
+ uniqueId: String? = "uniqueId",
+ cutout: DisplayCutout? = mock<DisplayCutout>()
+ ) =
+ mock<Display> {
+ whenever(this.displayAdjustments).thenReturn(DisplayAdjustments())
+ whenever(this.cutout).thenReturn(cutout)
+ whenever(this.uniqueId).thenReturn(uniqueId)
+ }
+ }
+}