Tests for StatusBarContentInsetsProvider

Reworked some functions for the status bar content insets calculations
and added tests for the content rectangle.

Test: atest StatusBarContentInsetsProviderTest
Bug: 187973222
Change-Id: I3eb473929944c19888f8c256b4c45ddd99948b88
This commit is contained in:
Evan Laird
2021-05-19 16:20:41 -04:00
parent dff37ee6d2
commit a5b595956d
2 changed files with 411 additions and 39 deletions

View File

@@ -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
@@ -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)
}
/**
@@ -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
@@ -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>) {
@@ -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
@@ -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)
}
}
/**
@@ -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
}

View File

@@ -0,0 +1,368 @@
/*
* 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))
}
}