Merge changes Iaaefbeb2,I3eb47392 into sc-dev am: d73b838304
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/14650174 Change-Id: Ica7c6e96bf94366b0889b9cf688eec18e74bae4d
This commit is contained in:
@@ -26,14 +26,18 @@ import android.os.Process
|
|||||||
import android.provider.DeviceConfig
|
import android.provider.DeviceConfig
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import com.android.systemui.Dumpable
|
||||||
|
|
||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.dagger.qualifiers.Main
|
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.phone.StatusBarWindowController
|
||||||
import com.android.systemui.statusbar.policy.CallbackController
|
import com.android.systemui.statusbar.policy.CallbackController
|
||||||
import com.android.systemui.util.Assert
|
import com.android.systemui.util.Assert
|
||||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||||
import com.android.systemui.util.time.SystemClock
|
import com.android.systemui.util.time.SystemClock
|
||||||
|
import java.io.FileDescriptor
|
||||||
|
import java.io.PrintWriter
|
||||||
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -59,9 +63,10 @@ class SystemStatusAnimationScheduler @Inject constructor(
|
|||||||
private val coordinator: SystemEventCoordinator,
|
private val coordinator: SystemEventCoordinator,
|
||||||
private val chipAnimationController: SystemEventChipAnimationController,
|
private val chipAnimationController: SystemEventChipAnimationController,
|
||||||
private val statusBarWindowController: StatusBarWindowController,
|
private val statusBarWindowController: StatusBarWindowController,
|
||||||
|
private val dumpManager: DumpManager,
|
||||||
private val systemClock: SystemClock,
|
private val systemClock: SystemClock,
|
||||||
@Main private val executor: DelayableExecutor
|
@Main private val executor: DelayableExecutor
|
||||||
) : CallbackController<SystemStatusAnimationCallback> {
|
) : CallbackController<SystemStatusAnimationCallback>, Dumpable {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val PROPERTY_ENABLE_IMMERSIVE_INDICATOR = "enable_immersive_indicator"
|
private const val PROPERTY_ENABLE_IMMERSIVE_INDICATOR = "enable_immersive_indicator"
|
||||||
@@ -71,10 +76,6 @@ class SystemStatusAnimationScheduler @Inject constructor(
|
|||||||
PROPERTY_ENABLE_IMMERSIVE_INDICATOR, true)
|
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
|
@SystemAnimationState var animationState: Int = IDLE
|
||||||
private set
|
private set
|
||||||
|
|
||||||
@@ -88,6 +89,7 @@ class SystemStatusAnimationScheduler @Inject constructor(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
coordinator.attachScheduler(this)
|
coordinator.attachScheduler(this)
|
||||||
|
dumpManager.registerDumpable(TAG, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onStatusEvent(event: StatusEvent) {
|
fun onStatusEvent(event: StatusEvent) {
|
||||||
@@ -293,6 +295,20 @@ class SystemStatusAnimationScheduler @Inject constructor(
|
|||||||
anim -> chipAnimationController.onChipAnimationUpdate(anim, animationState)
|
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(
|
inner class ChipAnimatorAdapter(
|
||||||
@SystemAnimationState val endState: Int,
|
@SystemAnimationState val endState: Int,
|
||||||
val viewCreator: (context: Context) -> View
|
val viewCreator: (context: Context) -> View
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import android.util.Pair
|
|||||||
import android.view.DisplayCutout
|
import android.view.DisplayCutout
|
||||||
import android.view.View.LAYOUT_DIRECTION_RTL
|
import android.view.View.LAYOUT_DIRECTION_RTL
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import android.view.WindowMetrics
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
import com.android.systemui.Dumpable
|
import com.android.systemui.Dumpable
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
@@ -118,17 +120,8 @@ class StatusBarContentInsetsProvider @Inject constructor(
|
|||||||
val chipWidth = rotatedResources.getDimensionPixelSize(
|
val chipWidth = rotatedResources.getDimensionPixelSize(
|
||||||
R.dimen.ongoing_appops_chip_max_width)
|
R.dimen.ongoing_appops_chip_max_width)
|
||||||
|
|
||||||
return if (context.resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL) {
|
val isRtl = context.resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL
|
||||||
Rect(insets.left - dotWidth,
|
return getPrivacyChipBoundingRectForInsets(insets, dotWidth, chipWidth, isRtl)
|
||||||
insets.top,
|
|
||||||
insets.left + chipWidth,
|
|
||||||
insets.bottom)
|
|
||||||
} else {
|
|
||||||
Rect(insets.right - chipWidth,
|
|
||||||
insets.top,
|
|
||||||
insets.right + dotWidth,
|
|
||||||
insets.bottom)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -139,8 +132,7 @@ class StatusBarContentInsetsProvider @Inject constructor(
|
|||||||
var insets = insetsByCorner[rotation]
|
var insets = insetsByCorner[rotation]
|
||||||
if (insets == null) {
|
if (insets == null) {
|
||||||
val rotatedResources = RotationUtils.getResourcesForRotation(rotation, context)
|
val rotatedResources = RotationUtils.getResourcesForRotation(rotation, context)
|
||||||
insets = getCalculatedInsetsForRotation(rotation, rotatedResources)
|
insets = getAndSetInsetsForRotation(rotation, rotatedResources)
|
||||||
insetsByCorner[rotation] = insets
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return insets
|
return insets
|
||||||
@@ -157,13 +149,19 @@ class StatusBarContentInsetsProvider @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getCalculatedInsetsForRotation(
|
private fun getCalculatedInsetsForRotation(
|
||||||
@Rotation rotation: Int,
|
@Rotation targetRotation: Int,
|
||||||
rotatedResources: Resources
|
rotatedResources: Resources
|
||||||
): Rect {
|
): Rect {
|
||||||
val dc = context.display.cutout
|
val dc = context.display.cutout
|
||||||
|
val currentRotation = RotationUtils.getExactRotation(context)
|
||||||
|
|
||||||
return calculateInsetsForRotationWithRotatedResources(
|
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>) {
|
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
|
||||||
@@ -179,8 +177,8 @@ interface StatusBarContentInsetsChangedListener {
|
|||||||
|
|
||||||
private const val TAG = "StatusBarInsetsProvider"
|
private const val TAG = "StatusBarInsetsProvider"
|
||||||
|
|
||||||
private fun getRotationZeroDisplayBounds(wm: WindowManager, @Rotation exactRotation: Int): Rect {
|
private fun getRotationZeroDisplayBounds(wm: WindowMetrics, @Rotation exactRotation: Int): Rect {
|
||||||
val bounds = wm.maximumWindowMetrics.bounds
|
val bounds = wm.bounds
|
||||||
|
|
||||||
if (exactRotation == ROTATION_NONE || exactRotation == ROTATION_UPSIDE_DOWN) {
|
if (exactRotation == ROTATION_NONE || exactRotation == ROTATION_UPSIDE_DOWN) {
|
||||||
return bounds
|
return bounds
|
||||||
@@ -190,9 +188,24 @@ private fun getRotationZeroDisplayBounds(wm: WindowManager, @Rotation exactRotat
|
|||||||
return Rect(0, 0, bounds.bottom, bounds.right)
|
return Rect(0, 0, bounds.bottom, bounds.right)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getCurrentDisplayBounds(wm: WindowManager): Rect {
|
@VisibleForTesting
|
||||||
val bounds = wm.maximumWindowMetrics.bounds
|
fun getPrivacyChipBoundingRectForInsets(
|
||||||
return bounds
|
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]
|
* @see [RotationUtils#getResourcesForRotation]
|
||||||
*/
|
*/
|
||||||
fun calculateInsetsForRotationWithRotatedResources(
|
fun calculateInsetsForRotationWithRotatedResources(
|
||||||
|
@Rotation currentRotation: Int,
|
||||||
@Rotation targetRotation: Int,
|
@Rotation targetRotation: Int,
|
||||||
rotatedResources: Resources,
|
|
||||||
displayCutout: DisplayCutout?,
|
displayCutout: DisplayCutout?,
|
||||||
windowmanager: WindowManager,
|
windowMetrics: WindowMetrics,
|
||||||
context: Context
|
statusBarHeight: Int,
|
||||||
|
roundedCornerPadding: Int
|
||||||
): Rect {
|
): 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
|
TODO: Check if this is ever used for devices with no rounded corners
|
||||||
val paddingStart = rotatedResources.getDimensionPixelSize(R.dimen.status_bar_padding_start)
|
val left = if (isRtl) paddingEnd else paddingStart
|
||||||
val paddingEnd = rotatedResources.getDimensionPixelSize(R.dimen.status_bar_padding_end)
|
val right = if (isRtl) paddingStart else paddingEnd
|
||||||
val left = if (rtl) paddingEnd else paddingStart
|
|
||||||
val right = if(rtl) paddingStart else paddingEnd
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
val roundedCornerPadding = rotatedResources.getDimensionPixelSize(
|
val rotZeroBounds = getRotationZeroDisplayBounds(windowMetrics, currentRotation)
|
||||||
R.dimen.rounded_corner_content_padding)
|
val currentBounds = windowMetrics.bounds
|
||||||
|
|
||||||
val rotZeroBounds = getRotationZeroDisplayBounds(windowmanager, exactRotation)
|
|
||||||
val currentBounds = getCurrentDisplayBounds(windowmanager)
|
|
||||||
|
|
||||||
val sbLeftRight = getStatusBarLeftRight(
|
val sbLeftRight = getStatusBarLeftRight(
|
||||||
displayCutout,
|
displayCutout,
|
||||||
height,
|
statusBarHeight,
|
||||||
rotZeroBounds.right,
|
rotZeroBounds.right,
|
||||||
rotZeroBounds.bottom,
|
rotZeroBounds.bottom,
|
||||||
currentBounds.width(),
|
currentBounds.width(),
|
||||||
currentBounds.height(),
|
currentBounds.height(),
|
||||||
roundedCornerPadding,
|
roundedCornerPadding,
|
||||||
targetRotation,
|
targetRotation,
|
||||||
exactRotation)
|
currentRotation)
|
||||||
|
|
||||||
return sbLeftRight
|
return sbLeftRight
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user