Loading packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt +21 −5 Original line number Diff line number Diff line Loading @@ -26,14 +26,18 @@ import android.os.Process import android.provider.DeviceConfig import android.util.Log import android.view.View import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.phone.StatusBarWindowController import com.android.systemui.statusbar.policy.CallbackController import com.android.systemui.util.Assert import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.time.SystemClock import java.io.FileDescriptor import java.io.PrintWriter import javax.inject.Inject Loading @@ -59,9 +63,10 @@ class SystemStatusAnimationScheduler @Inject constructor( private val coordinator: SystemEventCoordinator, private val chipAnimationController: SystemEventChipAnimationController, private val statusBarWindowController: StatusBarWindowController, private val dumpManager: DumpManager, private val systemClock: SystemClock, @Main private val executor: DelayableExecutor ) : CallbackController<SystemStatusAnimationCallback> { ) : CallbackController<SystemStatusAnimationCallback>, Dumpable { companion object { private const val PROPERTY_ENABLE_IMMERSIVE_INDICATOR = "enable_immersive_indicator" Loading @@ -71,10 +76,6 @@ class SystemStatusAnimationScheduler @Inject constructor( PROPERTY_ENABLE_IMMERSIVE_INDICATOR, true) } /** True from the time a scheduled event starts until it's animation finishes */ var isActive = false private set @SystemAnimationState var animationState: Int = IDLE private set Loading @@ -88,6 +89,7 @@ class SystemStatusAnimationScheduler @Inject constructor( init { coordinator.attachScheduler(this) dumpManager.registerDumpable(TAG, this) } fun onStatusEvent(event: StatusEvent) { Loading Loading @@ -293,6 +295,20 @@ class SystemStatusAnimationScheduler @Inject constructor( anim -> chipAnimationController.onChipAnimationUpdate(anim, animationState) } override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { pw.println("Scheduled event: $scheduledEvent") pw.println("Has persistent privacy dot: $hasPersistentDot") pw.println("Animation state: $animationState") pw.println("Listeners:") if (listeners.isEmpty()) { pw.println("(none)") } else { listeners.forEach { pw.println(" $it") } } } inner class ChipAnimatorAdapter( @SystemAnimationState val endState: Int, val viewCreator: (context: Context) -> View Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt +43 −39 Original line number Diff line number Diff line Loading @@ -23,6 +23,8 @@ import android.util.Pair import android.view.DisplayCutout import android.view.View.LAYOUT_DIRECTION_RTL import android.view.WindowManager import android.view.WindowMetrics import androidx.annotation.VisibleForTesting import com.android.systemui.Dumpable import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton Loading Loading @@ -118,17 +120,8 @@ class StatusBarContentInsetsProvider @Inject constructor( val chipWidth = rotatedResources.getDimensionPixelSize( R.dimen.ongoing_appops_chip_max_width) return if (context.resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL) { Rect(insets.left - dotWidth, insets.top, insets.left + chipWidth, insets.bottom) } else { Rect(insets.right - chipWidth, insets.top, insets.right + dotWidth, insets.bottom) } val isRtl = context.resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL return getPrivacyChipBoundingRectForInsets(insets, dotWidth, chipWidth, isRtl) } /** Loading @@ -139,8 +132,7 @@ class StatusBarContentInsetsProvider @Inject constructor( var insets = insetsByCorner[rotation] if (insets == null) { val rotatedResources = RotationUtils.getResourcesForRotation(rotation, context) insets = getCalculatedInsetsForRotation(rotation, rotatedResources) insetsByCorner[rotation] = insets insets = getAndSetInsetsForRotation(rotation, rotatedResources) } return insets Loading @@ -157,13 +149,19 @@ class StatusBarContentInsetsProvider @Inject constructor( } private fun getCalculatedInsetsForRotation( @Rotation rotation: Int, @Rotation targetRotation: Int, rotatedResources: Resources ): Rect { val dc = context.display.cutout val currentRotation = RotationUtils.getExactRotation(context) return calculateInsetsForRotationWithRotatedResources( rotation, rotatedResources, dc, windowManager, context) currentRotation, targetRotation, dc, windowManager.maximumWindowMetrics, rotatedResources.getDimensionPixelSize(R.dimen.status_bar_height), rotatedResources.getDimensionPixelSize(R.dimen.rounded_corner_content_padding)) } override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { Loading @@ -179,8 +177,8 @@ interface StatusBarContentInsetsChangedListener { private const val TAG = "StatusBarInsetsProvider" private fun getRotationZeroDisplayBounds(wm: WindowManager, @Rotation exactRotation: Int): Rect { val bounds = wm.maximumWindowMetrics.bounds private fun getRotationZeroDisplayBounds(wm: WindowMetrics, @Rotation exactRotation: Int): Rect { val bounds = wm.bounds if (exactRotation == ROTATION_NONE || exactRotation == ROTATION_UPSIDE_DOWN) { return bounds Loading @@ -190,9 +188,24 @@ private fun getRotationZeroDisplayBounds(wm: WindowManager, @Rotation exactRotat return Rect(0, 0, bounds.bottom, bounds.right) } private fun getCurrentDisplayBounds(wm: WindowManager): Rect { val bounds = wm.maximumWindowMetrics.bounds return bounds @VisibleForTesting fun getPrivacyChipBoundingRectForInsets( contentRect: Rect, dotWidth: Int, chipWidth: Int, isRtl: Boolean ): Rect { return if (isRtl) { Rect(contentRect.left - dotWidth, contentRect.top, contentRect.left + chipWidth, contentRect.bottom) } else { Rect(contentRect.right - chipWidth, contentRect.top, contentRect.right + dotWidth, contentRect.bottom) } } /** Loading @@ -206,41 +219,32 @@ private fun getCurrentDisplayBounds(wm: WindowManager): Rect { * @see [RotationUtils#getResourcesForRotation] */ fun calculateInsetsForRotationWithRotatedResources( @Rotation currentRotation: Int, @Rotation targetRotation: Int, rotatedResources: Resources, displayCutout: DisplayCutout?, windowmanager: WindowManager, context: Context windowMetrics: WindowMetrics, statusBarHeight: Int, roundedCornerPadding: Int ): Rect { val rtl = rotatedResources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL val exactRotation = RotationUtils.getExactRotation(context) val height = rotatedResources.getDimensionPixelSize(R.dimen.status_bar_height) /* TODO: Check if this is ever used for devices with no rounded corners val paddingStart = rotatedResources.getDimensionPixelSize(R.dimen.status_bar_padding_start) val paddingEnd = rotatedResources.getDimensionPixelSize(R.dimen.status_bar_padding_end) val left = if (rtl) paddingEnd else paddingStart val right = if(rtl) paddingStart else paddingEnd val left = if (isRtl) paddingEnd else paddingStart val right = if (isRtl) paddingStart else paddingEnd */ val roundedCornerPadding = rotatedResources.getDimensionPixelSize( R.dimen.rounded_corner_content_padding) val rotZeroBounds = getRotationZeroDisplayBounds(windowmanager, exactRotation) val currentBounds = getCurrentDisplayBounds(windowmanager) val rotZeroBounds = getRotationZeroDisplayBounds(windowMetrics, currentRotation) val currentBounds = windowMetrics.bounds val sbLeftRight = getStatusBarLeftRight( displayCutout, height, statusBarHeight, rotZeroBounds.right, rotZeroBounds.bottom, currentBounds.width(), currentBounds.height(), roundedCornerPadding, targetRotation, exactRotation) currentRotation) return sbLeftRight } Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt 0 → 100644 +368 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.statusbar.phone import android.graphics.Rect import android.test.suitebuilder.annotation.SmallTest import android.view.DisplayCutout import android.view.WindowMetrics import com.android.systemui.SysuiTestCase import com.android.systemui.util.leak.RotationUtils import com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE import com.android.systemui.util.leak.RotationUtils.ROTATION_NONE import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN import com.android.systemui.util.leak.RotationUtils.Rotation import junit.framework.Assert.assertTrue import org.junit.Before import org.junit.Test import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest class StatusBarContentInsetsProviderTest : SysuiTestCase() { @Mock private lateinit var dc: DisplayCutout @Mock private lateinit var windowMetrics: WindowMetrics @Before fun setup() { MockitoAnnotations.initMocks(this) } @Test fun testGetBoundingRectForPrivacyChipForRotation_noCutout() { val screenBounds = Rect(0, 0, 1080, 2160) val roundedCornerPadding = 20 val sbHeightPortrait = 100 val sbHeightLandscape = 60 val currentRotation = ROTATION_NONE val chipWidth = 30 val dotWidth = 10 `when`(windowMetrics.bounds).thenReturn(screenBounds) var isRtl = false var targetRotation = ROTATION_NONE var bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, null, windowMetrics, sbHeightPortrait, roundedCornerPadding) var chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl) /* 1080 - 20 (rounded corner) - 30 (chip), * 0 (sb top) * 1080 - 20 (rounded corner) + 10 ( dot), * 100 (sb height portrait) */ var expected = Rect(1030, 0, 1070, 100) assertRects(expected, chipBounds, currentRotation, targetRotation) isRtl = true chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl) /* 0 + 20 (rounded corner) - 10 (dot), * 0 (sb top) * 0 + 20 (rounded corner) + 30 (chip), * 100 (sb height portrait) */ expected = Rect(10, 0, 50, 100) assertRects(expected, chipBounds, currentRotation, targetRotation) isRtl = false targetRotation = ROTATION_LANDSCAPE bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, windowMetrics, sbHeightLandscape, roundedCornerPadding) chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl) /* 2160 - 20 (rounded corner) - 30 (chip), * 0 (sb top) * 2160 - 20 (rounded corner) + 10 ( dot), * 60 (sb height landscape) */ expected = Rect(2110, 0, 2150, 60) assertRects(expected, chipBounds, currentRotation, targetRotation) isRtl = true chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl) /* 0 + 20 (rounded corner) - 10 (dot), * 0 (sb top) * 0 + 20 (rounded corner) + 30 (chip), * 60 (sb height landscape) */ expected = Rect(10, 0, 50, 60) assertRects(expected, chipBounds, currentRotation, targetRotation) } @Test fun testCalculateInsetsForRotationWithRotatedResources_topLeftCutout() { // 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 roundedCornerPadding = 20 val sbHeightPortrait = 100 val sbHeightLandscape = 60 val currentRotation = ROTATION_NONE `when`(windowMetrics.bounds).thenReturn(screenBounds) `when`(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(dcBounds.right, 0, screenBounds.right - roundedCornerPadding, sbHeightPortrait) var bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, windowMetrics, sbHeightPortrait, roundedCornerPadding) assertRects(expectedBounds, bounds, currentRotation, targetRotation) targetRotation = ROTATION_LANDSCAPE expectedBounds = Rect(dcBounds.height(), 0, screenBounds.height() - roundedCornerPadding, sbHeightLandscape) bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, windowMetrics, sbHeightLandscape, roundedCornerPadding) 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(roundedCornerPadding, 0, screenBounds.width() - roundedCornerPadding, sbHeightPortrait) bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, windowMetrics, sbHeightPortrait, roundedCornerPadding) assertRects(expectedBounds, bounds, currentRotation, targetRotation) // Phone in portrait, seascape (rot_270) bounds targetRotation = ROTATION_SEASCAPE expectedBounds = Rect(roundedCornerPadding, 0, screenBounds.height() - dcBounds.height(), sbHeightLandscape) bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, windowMetrics, sbHeightLandscape, roundedCornerPadding) assertRects(expectedBounds, bounds, currentRotation, targetRotation) } @Test fun testCalculateInsetsForRotationWithRotatedResources_nonCornerCutout() { // GIVEN phone in portrait mode, where width < height and the cutout is not in the corner // the assumption here is that if the cutout does NOT touch the corner then we have room to // layout the status bar in the given space. val screenBounds = Rect(0, 0, 1080, 2160) // cutout centered at the top val dcBounds = Rect(490, 0, 590, 100) val roundedCornerPadding = 20 val sbHeightPortrait = 100 val sbHeightLandscape = 60 val currentRotation = ROTATION_NONE `when`(windowMetrics.bounds).thenReturn(screenBounds) `when`(dc.boundingRects).thenReturn(listOf(dcBounds)) // THEN only the landscape/seascape rotations should avoid the cutout area because of the // potential letterboxing var targetRotation = ROTATION_NONE var expectedBounds = Rect(roundedCornerPadding, 0, screenBounds.right - roundedCornerPadding, sbHeightPortrait) var bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, windowMetrics, sbHeightPortrait, roundedCornerPadding) assertRects(expectedBounds, bounds, currentRotation, targetRotation) targetRotation = ROTATION_LANDSCAPE expectedBounds = Rect(dcBounds.height(), 0, screenBounds.height() - roundedCornerPadding, sbHeightLandscape) bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, windowMetrics, sbHeightLandscape, roundedCornerPadding) assertRects(expectedBounds, bounds, currentRotation, targetRotation) targetRotation = ROTATION_UPSIDE_DOWN expectedBounds = Rect(roundedCornerPadding, 0, screenBounds.right - roundedCornerPadding, sbHeightPortrait) bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, windowMetrics, sbHeightPortrait, roundedCornerPadding) assertRects(expectedBounds, bounds, currentRotation, targetRotation) targetRotation = ROTATION_SEASCAPE expectedBounds = Rect(roundedCornerPadding, 0, screenBounds.height() - dcBounds.height(), sbHeightLandscape) bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, windowMetrics, sbHeightLandscape, roundedCornerPadding) assertRects(expectedBounds, bounds, currentRotation, targetRotation) } @Test fun testCalculateInsetsForRotationWithRotatedResources_noCutout() { // GIVEN device in portrait mode, where width < height and no cutout val currentRotation = ROTATION_NONE val screenBounds = Rect(0, 0, 1080, 2160) val roundedCornerPadding = 20 val sbHeightPortrait = 100 val sbHeightLandscape = 60 `when`(windowMetrics.bounds).thenReturn(screenBounds) // THEN content insets should only use rounded corner padding var targetRotation = ROTATION_NONE var expectedBounds = Rect(roundedCornerPadding, 0, screenBounds.right - roundedCornerPadding, sbHeightPortrait) var bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, null, /* no cutout */ windowMetrics, sbHeightPortrait, roundedCornerPadding) assertRects(expectedBounds, bounds, currentRotation, targetRotation) targetRotation = ROTATION_LANDSCAPE expectedBounds = Rect(roundedCornerPadding, 0, screenBounds.height() - roundedCornerPadding, sbHeightLandscape) bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, null, /* no cutout */ windowMetrics, sbHeightLandscape, roundedCornerPadding) assertRects(expectedBounds, bounds, currentRotation, targetRotation) targetRotation = ROTATION_UPSIDE_DOWN expectedBounds = Rect(roundedCornerPadding, 0, screenBounds.width() - roundedCornerPadding, sbHeightPortrait) bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, null, /* no cutout */ windowMetrics, sbHeightPortrait, roundedCornerPadding) assertRects(expectedBounds, bounds, currentRotation, targetRotation) targetRotation = ROTATION_LANDSCAPE expectedBounds = Rect(roundedCornerPadding, 0, screenBounds.height() - roundedCornerPadding, sbHeightLandscape) bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, null, /* no cutout */ windowMetrics, sbHeightLandscape, roundedCornerPadding) assertRects(expectedBounds, bounds, currentRotation, targetRotation) } private fun assertRects( expected: Rect, actual: Rect, @Rotation currentRotation: Int, @Rotation targetRotation: Int ) { assertTrue( "Rects must match. currentRotation=${RotationUtils.toString(currentRotation)}" + " targetRotation=${RotationUtils.toString(targetRotation)}" + " expected=$expected actual=$actual", expected.equals(actual)) } } No newline at end of file Loading
packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt +21 −5 Original line number Diff line number Diff line Loading @@ -26,14 +26,18 @@ import android.os.Process import android.provider.DeviceConfig import android.util.Log import android.view.View import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.phone.StatusBarWindowController import com.android.systemui.statusbar.policy.CallbackController import com.android.systemui.util.Assert import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.time.SystemClock import java.io.FileDescriptor import java.io.PrintWriter import javax.inject.Inject Loading @@ -59,9 +63,10 @@ class SystemStatusAnimationScheduler @Inject constructor( private val coordinator: SystemEventCoordinator, private val chipAnimationController: SystemEventChipAnimationController, private val statusBarWindowController: StatusBarWindowController, private val dumpManager: DumpManager, private val systemClock: SystemClock, @Main private val executor: DelayableExecutor ) : CallbackController<SystemStatusAnimationCallback> { ) : CallbackController<SystemStatusAnimationCallback>, Dumpable { companion object { private const val PROPERTY_ENABLE_IMMERSIVE_INDICATOR = "enable_immersive_indicator" Loading @@ -71,10 +76,6 @@ class SystemStatusAnimationScheduler @Inject constructor( PROPERTY_ENABLE_IMMERSIVE_INDICATOR, true) } /** True from the time a scheduled event starts until it's animation finishes */ var isActive = false private set @SystemAnimationState var animationState: Int = IDLE private set Loading @@ -88,6 +89,7 @@ class SystemStatusAnimationScheduler @Inject constructor( init { coordinator.attachScheduler(this) dumpManager.registerDumpable(TAG, this) } fun onStatusEvent(event: StatusEvent) { Loading Loading @@ -293,6 +295,20 @@ class SystemStatusAnimationScheduler @Inject constructor( anim -> chipAnimationController.onChipAnimationUpdate(anim, animationState) } override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { pw.println("Scheduled event: $scheduledEvent") pw.println("Has persistent privacy dot: $hasPersistentDot") pw.println("Animation state: $animationState") pw.println("Listeners:") if (listeners.isEmpty()) { pw.println("(none)") } else { listeners.forEach { pw.println(" $it") } } } inner class ChipAnimatorAdapter( @SystemAnimationState val endState: Int, val viewCreator: (context: Context) -> View Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt +43 −39 Original line number Diff line number Diff line Loading @@ -23,6 +23,8 @@ import android.util.Pair import android.view.DisplayCutout import android.view.View.LAYOUT_DIRECTION_RTL import android.view.WindowManager import android.view.WindowMetrics import androidx.annotation.VisibleForTesting import com.android.systemui.Dumpable import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton Loading Loading @@ -118,17 +120,8 @@ class StatusBarContentInsetsProvider @Inject constructor( val chipWidth = rotatedResources.getDimensionPixelSize( R.dimen.ongoing_appops_chip_max_width) return if (context.resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL) { Rect(insets.left - dotWidth, insets.top, insets.left + chipWidth, insets.bottom) } else { Rect(insets.right - chipWidth, insets.top, insets.right + dotWidth, insets.bottom) } val isRtl = context.resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL return getPrivacyChipBoundingRectForInsets(insets, dotWidth, chipWidth, isRtl) } /** Loading @@ -139,8 +132,7 @@ class StatusBarContentInsetsProvider @Inject constructor( var insets = insetsByCorner[rotation] if (insets == null) { val rotatedResources = RotationUtils.getResourcesForRotation(rotation, context) insets = getCalculatedInsetsForRotation(rotation, rotatedResources) insetsByCorner[rotation] = insets insets = getAndSetInsetsForRotation(rotation, rotatedResources) } return insets Loading @@ -157,13 +149,19 @@ class StatusBarContentInsetsProvider @Inject constructor( } private fun getCalculatedInsetsForRotation( @Rotation rotation: Int, @Rotation targetRotation: Int, rotatedResources: Resources ): Rect { val dc = context.display.cutout val currentRotation = RotationUtils.getExactRotation(context) return calculateInsetsForRotationWithRotatedResources( rotation, rotatedResources, dc, windowManager, context) currentRotation, targetRotation, dc, windowManager.maximumWindowMetrics, rotatedResources.getDimensionPixelSize(R.dimen.status_bar_height), rotatedResources.getDimensionPixelSize(R.dimen.rounded_corner_content_padding)) } override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { Loading @@ -179,8 +177,8 @@ interface StatusBarContentInsetsChangedListener { private const val TAG = "StatusBarInsetsProvider" private fun getRotationZeroDisplayBounds(wm: WindowManager, @Rotation exactRotation: Int): Rect { val bounds = wm.maximumWindowMetrics.bounds private fun getRotationZeroDisplayBounds(wm: WindowMetrics, @Rotation exactRotation: Int): Rect { val bounds = wm.bounds if (exactRotation == ROTATION_NONE || exactRotation == ROTATION_UPSIDE_DOWN) { return bounds Loading @@ -190,9 +188,24 @@ private fun getRotationZeroDisplayBounds(wm: WindowManager, @Rotation exactRotat return Rect(0, 0, bounds.bottom, bounds.right) } private fun getCurrentDisplayBounds(wm: WindowManager): Rect { val bounds = wm.maximumWindowMetrics.bounds return bounds @VisibleForTesting fun getPrivacyChipBoundingRectForInsets( contentRect: Rect, dotWidth: Int, chipWidth: Int, isRtl: Boolean ): Rect { return if (isRtl) { Rect(contentRect.left - dotWidth, contentRect.top, contentRect.left + chipWidth, contentRect.bottom) } else { Rect(contentRect.right - chipWidth, contentRect.top, contentRect.right + dotWidth, contentRect.bottom) } } /** Loading @@ -206,41 +219,32 @@ private fun getCurrentDisplayBounds(wm: WindowManager): Rect { * @see [RotationUtils#getResourcesForRotation] */ fun calculateInsetsForRotationWithRotatedResources( @Rotation currentRotation: Int, @Rotation targetRotation: Int, rotatedResources: Resources, displayCutout: DisplayCutout?, windowmanager: WindowManager, context: Context windowMetrics: WindowMetrics, statusBarHeight: Int, roundedCornerPadding: Int ): Rect { val rtl = rotatedResources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL val exactRotation = RotationUtils.getExactRotation(context) val height = rotatedResources.getDimensionPixelSize(R.dimen.status_bar_height) /* TODO: Check if this is ever used for devices with no rounded corners val paddingStart = rotatedResources.getDimensionPixelSize(R.dimen.status_bar_padding_start) val paddingEnd = rotatedResources.getDimensionPixelSize(R.dimen.status_bar_padding_end) val left = if (rtl) paddingEnd else paddingStart val right = if(rtl) paddingStart else paddingEnd val left = if (isRtl) paddingEnd else paddingStart val right = if (isRtl) paddingStart else paddingEnd */ val roundedCornerPadding = rotatedResources.getDimensionPixelSize( R.dimen.rounded_corner_content_padding) val rotZeroBounds = getRotationZeroDisplayBounds(windowmanager, exactRotation) val currentBounds = getCurrentDisplayBounds(windowmanager) val rotZeroBounds = getRotationZeroDisplayBounds(windowMetrics, currentRotation) val currentBounds = windowMetrics.bounds val sbLeftRight = getStatusBarLeftRight( displayCutout, height, statusBarHeight, rotZeroBounds.right, rotZeroBounds.bottom, currentBounds.width(), currentBounds.height(), roundedCornerPadding, targetRotation, exactRotation) currentRotation) return sbLeftRight } Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt 0 → 100644 +368 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.statusbar.phone import android.graphics.Rect import android.test.suitebuilder.annotation.SmallTest import android.view.DisplayCutout import android.view.WindowMetrics import com.android.systemui.SysuiTestCase import com.android.systemui.util.leak.RotationUtils import com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE import com.android.systemui.util.leak.RotationUtils.ROTATION_NONE import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN import com.android.systemui.util.leak.RotationUtils.Rotation import junit.framework.Assert.assertTrue import org.junit.Before import org.junit.Test import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest class StatusBarContentInsetsProviderTest : SysuiTestCase() { @Mock private lateinit var dc: DisplayCutout @Mock private lateinit var windowMetrics: WindowMetrics @Before fun setup() { MockitoAnnotations.initMocks(this) } @Test fun testGetBoundingRectForPrivacyChipForRotation_noCutout() { val screenBounds = Rect(0, 0, 1080, 2160) val roundedCornerPadding = 20 val sbHeightPortrait = 100 val sbHeightLandscape = 60 val currentRotation = ROTATION_NONE val chipWidth = 30 val dotWidth = 10 `when`(windowMetrics.bounds).thenReturn(screenBounds) var isRtl = false var targetRotation = ROTATION_NONE var bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, null, windowMetrics, sbHeightPortrait, roundedCornerPadding) var chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl) /* 1080 - 20 (rounded corner) - 30 (chip), * 0 (sb top) * 1080 - 20 (rounded corner) + 10 ( dot), * 100 (sb height portrait) */ var expected = Rect(1030, 0, 1070, 100) assertRects(expected, chipBounds, currentRotation, targetRotation) isRtl = true chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl) /* 0 + 20 (rounded corner) - 10 (dot), * 0 (sb top) * 0 + 20 (rounded corner) + 30 (chip), * 100 (sb height portrait) */ expected = Rect(10, 0, 50, 100) assertRects(expected, chipBounds, currentRotation, targetRotation) isRtl = false targetRotation = ROTATION_LANDSCAPE bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, windowMetrics, sbHeightLandscape, roundedCornerPadding) chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl) /* 2160 - 20 (rounded corner) - 30 (chip), * 0 (sb top) * 2160 - 20 (rounded corner) + 10 ( dot), * 60 (sb height landscape) */ expected = Rect(2110, 0, 2150, 60) assertRects(expected, chipBounds, currentRotation, targetRotation) isRtl = true chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl) /* 0 + 20 (rounded corner) - 10 (dot), * 0 (sb top) * 0 + 20 (rounded corner) + 30 (chip), * 60 (sb height landscape) */ expected = Rect(10, 0, 50, 60) assertRects(expected, chipBounds, currentRotation, targetRotation) } @Test fun testCalculateInsetsForRotationWithRotatedResources_topLeftCutout() { // 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 roundedCornerPadding = 20 val sbHeightPortrait = 100 val sbHeightLandscape = 60 val currentRotation = ROTATION_NONE `when`(windowMetrics.bounds).thenReturn(screenBounds) `when`(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(dcBounds.right, 0, screenBounds.right - roundedCornerPadding, sbHeightPortrait) var bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, windowMetrics, sbHeightPortrait, roundedCornerPadding) assertRects(expectedBounds, bounds, currentRotation, targetRotation) targetRotation = ROTATION_LANDSCAPE expectedBounds = Rect(dcBounds.height(), 0, screenBounds.height() - roundedCornerPadding, sbHeightLandscape) bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, windowMetrics, sbHeightLandscape, roundedCornerPadding) 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(roundedCornerPadding, 0, screenBounds.width() - roundedCornerPadding, sbHeightPortrait) bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, windowMetrics, sbHeightPortrait, roundedCornerPadding) assertRects(expectedBounds, bounds, currentRotation, targetRotation) // Phone in portrait, seascape (rot_270) bounds targetRotation = ROTATION_SEASCAPE expectedBounds = Rect(roundedCornerPadding, 0, screenBounds.height() - dcBounds.height(), sbHeightLandscape) bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, windowMetrics, sbHeightLandscape, roundedCornerPadding) assertRects(expectedBounds, bounds, currentRotation, targetRotation) } @Test fun testCalculateInsetsForRotationWithRotatedResources_nonCornerCutout() { // GIVEN phone in portrait mode, where width < height and the cutout is not in the corner // the assumption here is that if the cutout does NOT touch the corner then we have room to // layout the status bar in the given space. val screenBounds = Rect(0, 0, 1080, 2160) // cutout centered at the top val dcBounds = Rect(490, 0, 590, 100) val roundedCornerPadding = 20 val sbHeightPortrait = 100 val sbHeightLandscape = 60 val currentRotation = ROTATION_NONE `when`(windowMetrics.bounds).thenReturn(screenBounds) `when`(dc.boundingRects).thenReturn(listOf(dcBounds)) // THEN only the landscape/seascape rotations should avoid the cutout area because of the // potential letterboxing var targetRotation = ROTATION_NONE var expectedBounds = Rect(roundedCornerPadding, 0, screenBounds.right - roundedCornerPadding, sbHeightPortrait) var bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, windowMetrics, sbHeightPortrait, roundedCornerPadding) assertRects(expectedBounds, bounds, currentRotation, targetRotation) targetRotation = ROTATION_LANDSCAPE expectedBounds = Rect(dcBounds.height(), 0, screenBounds.height() - roundedCornerPadding, sbHeightLandscape) bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, windowMetrics, sbHeightLandscape, roundedCornerPadding) assertRects(expectedBounds, bounds, currentRotation, targetRotation) targetRotation = ROTATION_UPSIDE_DOWN expectedBounds = Rect(roundedCornerPadding, 0, screenBounds.right - roundedCornerPadding, sbHeightPortrait) bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, windowMetrics, sbHeightPortrait, roundedCornerPadding) assertRects(expectedBounds, bounds, currentRotation, targetRotation) targetRotation = ROTATION_SEASCAPE expectedBounds = Rect(roundedCornerPadding, 0, screenBounds.height() - dcBounds.height(), sbHeightLandscape) bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, dc, windowMetrics, sbHeightLandscape, roundedCornerPadding) assertRects(expectedBounds, bounds, currentRotation, targetRotation) } @Test fun testCalculateInsetsForRotationWithRotatedResources_noCutout() { // GIVEN device in portrait mode, where width < height and no cutout val currentRotation = ROTATION_NONE val screenBounds = Rect(0, 0, 1080, 2160) val roundedCornerPadding = 20 val sbHeightPortrait = 100 val sbHeightLandscape = 60 `when`(windowMetrics.bounds).thenReturn(screenBounds) // THEN content insets should only use rounded corner padding var targetRotation = ROTATION_NONE var expectedBounds = Rect(roundedCornerPadding, 0, screenBounds.right - roundedCornerPadding, sbHeightPortrait) var bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, null, /* no cutout */ windowMetrics, sbHeightPortrait, roundedCornerPadding) assertRects(expectedBounds, bounds, currentRotation, targetRotation) targetRotation = ROTATION_LANDSCAPE expectedBounds = Rect(roundedCornerPadding, 0, screenBounds.height() - roundedCornerPadding, sbHeightLandscape) bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, null, /* no cutout */ windowMetrics, sbHeightLandscape, roundedCornerPadding) assertRects(expectedBounds, bounds, currentRotation, targetRotation) targetRotation = ROTATION_UPSIDE_DOWN expectedBounds = Rect(roundedCornerPadding, 0, screenBounds.width() - roundedCornerPadding, sbHeightPortrait) bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, null, /* no cutout */ windowMetrics, sbHeightPortrait, roundedCornerPadding) assertRects(expectedBounds, bounds, currentRotation, targetRotation) targetRotation = ROTATION_LANDSCAPE expectedBounds = Rect(roundedCornerPadding, 0, screenBounds.height() - roundedCornerPadding, sbHeightLandscape) bounds = calculateInsetsForRotationWithRotatedResources( currentRotation, targetRotation, null, /* no cutout */ windowMetrics, sbHeightLandscape, roundedCornerPadding) assertRects(expectedBounds, bounds, currentRotation, targetRotation) } private fun assertRects( expected: Rect, actual: Rect, @Rotation currentRotation: Int, @Rotation targetRotation: Int ) { assertTrue( "Rects must match. currentRotation=${RotationUtils.toString(currentRotation)}" + " targetRotation=${RotationUtils.toString(targetRotation)}" + " expected=$expected actual=$actual", expected.equals(actual)) } } No newline at end of file