Merge changes Iaaefbeb2,I3eb47392 into sc-dev

* changes:
  Add some dumpsys information to SystemStatusAnimationScheduler
  Tests for StatusBarContentInsetsProvider
This commit is contained in:
Evan Laird
2021-05-26 16:58:01 +00:00
committed by Android (Google) Code Review
3 changed files with 432 additions and 44 deletions

View File

@@ -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
@@ -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"
@@ -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
@@ -88,6 +89,7 @@ class SystemStatusAnimationScheduler @Inject constructor(
init {
coordinator.attachScheduler(this)
dumpManager.registerDumpable(TAG, this)
}
fun onStatusEvent(event: StatusEvent) {
@@ -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

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))
}
}