Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt→packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt +371 −34 Original line number Diff line number Diff line Loading @@ -21,7 +21,11 @@ import android.content.res.Configuration 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 Loading @@ -38,30 +42,30 @@ import com.google.common.truth.Truth.assertThat 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) Loading Loading @@ -117,7 +121,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, sysUICutout, screenBounds, sbHeightLandscape, minLeftPadding, Loading Loading @@ -161,7 +165,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { } @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) Loading @@ -174,7 +178,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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 Loading @@ -187,7 +191,224 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { var bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, 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_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, Loading @@ -208,7 +429,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, sysUICutout, screenBounds, sbHeightLandscape, minLeftPadding, Loading @@ -231,7 +452,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, sysUICutout, screenBounds, sbHeightPortrait, minLeftPadding, Loading @@ -253,7 +474,118 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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_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, Loading @@ -273,7 +605,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { val bounds = calculateInsetsForRotationWithRotatedResources( currentRotation = ROTATION_NONE, targetRotation = ROTATION_NONE, displayCutout = dc, sysUICutout = sysUICutout, maxBounds = Rect(0, 0, 1080, 2160), statusBarHeight = 100, minLeft = 0, Loading @@ -293,7 +625,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { val bounds = calculateInsetsForRotationWithRotatedResources( currentRotation = ROTATION_NONE, targetRotation = ROTATION_NONE, displayCutout = dc, sysUICutout = sysUICutout, maxBounds = Rect(0, 0, 1080, 2160), statusBarHeight = 100, minLeft = 0, Loading Loading @@ -321,6 +653,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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 Loading @@ -330,7 +663,11 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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 Loading @@ -343,7 +680,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { var bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, sysUICutout = sysUICutout, screenBounds, sbHeightPortrait, minLeftPadding, Loading @@ -364,7 +701,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, sysUICutout = sysUICutout, screenBounds, sbHeightLandscape, minLeftPadding, Loading @@ -385,7 +722,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, sysUICutout = sysUICutout, screenBounds, sbHeightPortrait, minLeftPadding, Loading @@ -406,7 +743,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, sysUICutout = sysUICutout, screenBounds, sbHeightLandscape, minLeftPadding, Loading Loading @@ -528,7 +865,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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 Loading @@ -540,7 +877,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { val bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, sysUICutout, screenBounds, sbHeightPortrait, minLeftPadding, Loading @@ -557,7 +894,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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) Loading @@ -576,7 +913,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { // 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 Loading @@ -602,7 +939,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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 Loading @@ -624,7 +961,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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 Loading @@ -645,7 +982,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { @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 Loading packages/SystemUI/res/values/config.xml +5 −0 Original line number Diff line number Diff line Loading @@ -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> Loading @@ -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" --> Loading packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt +1 −0 Original line number Diff line number Diff line Loading @@ -24,4 +24,5 @@ data class CameraProtectionInfo( val physicalCameraId: String?, val cutoutProtectionPath: Path, val cutoutBounds: Rect, val displayUniqueId: String?, ) packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt +16 −6 File changed.Preview size limit exceeded, changes collapsed. Show changes packages/SystemUI/src/com/android/systemui/CameraProtectionModule.kt 0 → 100644 +26 −0 Original line number Diff line number Diff line /* * 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 } Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt→packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt +371 −34 Original line number Diff line number Diff line Loading @@ -21,7 +21,11 @@ import android.content.res.Configuration 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 Loading @@ -38,30 +42,30 @@ import com.google.common.truth.Truth.assertThat 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) Loading Loading @@ -117,7 +121,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, sysUICutout, screenBounds, sbHeightLandscape, minLeftPadding, Loading Loading @@ -161,7 +165,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { } @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) Loading @@ -174,7 +178,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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 Loading @@ -187,7 +191,224 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { var bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, 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_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, Loading @@ -208,7 +429,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, sysUICutout, screenBounds, sbHeightLandscape, minLeftPadding, Loading @@ -231,7 +452,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, sysUICutout, screenBounds, sbHeightPortrait, minLeftPadding, Loading @@ -253,7 +474,118 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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_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, Loading @@ -273,7 +605,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { val bounds = calculateInsetsForRotationWithRotatedResources( currentRotation = ROTATION_NONE, targetRotation = ROTATION_NONE, displayCutout = dc, sysUICutout = sysUICutout, maxBounds = Rect(0, 0, 1080, 2160), statusBarHeight = 100, minLeft = 0, Loading @@ -293,7 +625,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { val bounds = calculateInsetsForRotationWithRotatedResources( currentRotation = ROTATION_NONE, targetRotation = ROTATION_NONE, displayCutout = dc, sysUICutout = sysUICutout, maxBounds = Rect(0, 0, 1080, 2160), statusBarHeight = 100, minLeft = 0, Loading Loading @@ -321,6 +653,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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 Loading @@ -330,7 +663,11 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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 Loading @@ -343,7 +680,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { var bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, sysUICutout = sysUICutout, screenBounds, sbHeightPortrait, minLeftPadding, Loading @@ -364,7 +701,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, sysUICutout = sysUICutout, screenBounds, sbHeightLandscape, minLeftPadding, Loading @@ -385,7 +722,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, sysUICutout = sysUICutout, screenBounds, sbHeightPortrait, minLeftPadding, Loading @@ -406,7 +743,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, sysUICutout = sysUICutout, screenBounds, sbHeightLandscape, minLeftPadding, Loading Loading @@ -528,7 +865,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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 Loading @@ -540,7 +877,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { val bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, sysUICutout, screenBounds, sbHeightPortrait, minLeftPadding, Loading @@ -557,7 +894,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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) Loading @@ -576,7 +913,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { // 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 Loading @@ -602,7 +939,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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 Loading @@ -624,7 +961,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { 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 Loading @@ -645,7 +982,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { @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 Loading
packages/SystemUI/res/values/config.xml +5 −0 Original line number Diff line number Diff line Loading @@ -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> Loading @@ -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" --> Loading
packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt +1 −0 Original line number Diff line number Diff line Loading @@ -24,4 +24,5 @@ data class CameraProtectionInfo( val physicalCameraId: String?, val cutoutProtectionPath: Path, val cutoutBounds: Rect, val displayUniqueId: String?, )
packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt +16 −6 File changed.Preview size limit exceeded, changes collapsed. Show changes
packages/SystemUI/src/com/android/systemui/CameraProtectionModule.kt 0 → 100644 +26 −0 Original line number Diff line number Diff line /* * 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 }