Merge "Retain old logic for prohibiting battery estimate on the devices with center cutout." into tm-qpr-dev am: ce066a86b1
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21317121
Change-Id: Ided7acf29b5a545e88ecd4c1d1215a7b7bc4b4b1
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 946fe54..e696d13 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -152,9 +152,7 @@
Configuration config = mContext.getResources().getConfiguration();
setDatePrivacyContainersWidth(config.orientation == Configuration.ORIENTATION_LANDSCAPE);
- // QS will always show the estimate, and BatteryMeterView handles the case where
- // it's unavailable or charging
- mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);
+ updateBatteryMode();
mIconsAlphaAnimatorFixed = new TouchAnimator.Builder()
.addFloat(mIconContainer, "alpha", 0, 1)
@@ -460,24 +458,24 @@
(LinearLayout.LayoutParams) mDatePrivacySeparator.getLayoutParams();
LinearLayout.LayoutParams mClockIconsSeparatorLayoutParams =
(LinearLayout.LayoutParams) mClockIconsSeparator.getLayoutParams();
- if (cutout != null) {
- Rect topCutout = cutout.getBoundingRectTop();
- if (topCutout.isEmpty() || hasCornerCutout) {
- datePrivacySeparatorLayoutParams.width = 0;
- mDatePrivacySeparator.setVisibility(View.GONE);
- mClockIconsSeparatorLayoutParams.width = 0;
- setSeparatorVisibility(false);
- mShowClockIconsSeparator = false;
- mHasCenterCutout = false;
- } else {
- datePrivacySeparatorLayoutParams.width = topCutout.width();
- mDatePrivacySeparator.setVisibility(View.VISIBLE);
- mClockIconsSeparatorLayoutParams.width = topCutout.width();
- mShowClockIconsSeparator = true;
- setSeparatorVisibility(mKeyguardExpansionFraction == 0f);
- mHasCenterCutout = true;
- }
+
+ Rect topCutout = cutout == null ? null : cutout.getBoundingRectTop();
+ if (topCutout == null || topCutout.isEmpty() || hasCornerCutout) {
+ datePrivacySeparatorLayoutParams.width = 0;
+ mDatePrivacySeparator.setVisibility(View.GONE);
+ mClockIconsSeparatorLayoutParams.width = 0;
+ setSeparatorVisibility(false);
+ mShowClockIconsSeparator = false;
+ mHasCenterCutout = false;
+ } else {
+ datePrivacySeparatorLayoutParams.width = topCutout.width();
+ mDatePrivacySeparator.setVisibility(View.VISIBLE);
+ mClockIconsSeparatorLayoutParams.width = topCutout.width();
+ mShowClockIconsSeparator = true;
+ setSeparatorVisibility(mKeyguardExpansionFraction == 0f);
+ mHasCenterCutout = true;
}
+
mDatePrivacySeparator.setLayoutParams(datePrivacySeparatorLayoutParams);
mClockIconsSeparator.setLayoutParams(mClockIconsSeparatorLayoutParams);
mCutOutPaddingLeft = sbInsets.first;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index 197232e..80f7c36 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -24,6 +24,7 @@
import android.os.Trace
import android.os.Trace.TRACE_TAG_APP
import android.util.Pair
+import android.view.DisplayCutout
import android.view.View
import android.view.WindowInsets
import android.widget.TextView
@@ -91,7 +92,8 @@
private val featureFlags: FeatureFlags,
private val qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder,
private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager,
- private val demoModeController: DemoModeController
+ private val demoModeController: DemoModeController,
+ private val qsBatteryModeController: QsBatteryModeController,
) : ViewController<View>(header), Dumpable {
companion object {
@@ -129,9 +131,8 @@
private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons)
private val qsCarrierGroup: QSCarrierGroup = header.findViewById(R.id.carrier_group)
- private var cutoutLeft = 0
- private var cutoutRight = 0
private var roundedCorners = 0
+ private var cutout: DisplayCutout? = null
private var lastInsets: WindowInsets? = null
private var qsDisabled = false
@@ -273,7 +274,6 @@
// battery settings same as in QS icons
batteryMeterViewController.ignoreTunerUpdates()
- batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
iconManager = tintedIconManagerFactory.create(iconContainer, StatusBarLocation.QS)
iconManager.setTint(
@@ -305,6 +305,7 @@
if (header is MotionLayout) {
header.setOnApplyWindowInsetsListener(insetListener)
+
clock.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
val newPivot = if (v.isLayoutRtl) v.width.toFloat() else 0f
v.pivotX = newPivot
@@ -376,11 +377,13 @@
}
private fun updateConstraintsForInsets(view: MotionLayout, insets: WindowInsets) {
- val cutout = insets.displayCutout
+ val cutout = insets.displayCutout.also {
+ this.cutout = it
+ }
val sbInsets: Pair<Int, Int> = insetsProvider.getStatusBarContentInsetsForCurrentRotation()
- cutoutLeft = sbInsets.first
- cutoutRight = sbInsets.second
+ val cutoutLeft = sbInsets.first
+ val cutoutRight = sbInsets.second
val hasCornerCutout: Boolean = insetsProvider.currentRotationHasCornerCutout()
updateQQSPaddings()
// Set these guides as the left/right limits for content that lives in the top row, using
@@ -408,6 +411,13 @@
}
view.updateAllConstraints(changes)
+ updateBatteryMode()
+ }
+
+ private fun updateBatteryMode() {
+ qsBatteryModeController.getBatteryMode(cutout, qsExpandedFraction)?.let {
+ batteryIcon.setPercentShowMode(it)
+ }
}
private fun updateScrollY() {
@@ -475,6 +485,7 @@
if (header is MotionLayout && !largeScreenActive && visible) {
logInstantEvent("updatePosition: $qsExpandedFraction")
header.progress = qsExpandedFraction
+ updateBatteryMode()
}
}
@@ -511,6 +522,7 @@
val padding = resources.getDimensionPixelSize(R.dimen.qs_panel_padding)
header.setPadding(padding, header.paddingTop, padding, header.paddingBottom)
updateQQSPaddings()
+ qsBatteryModeController.updateResources()
}
private fun updateQQSPaddings() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt b/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt
new file mode 100644
index 0000000..3eec7fa0e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt
@@ -0,0 +1,70 @@
+package com.android.systemui.shade
+
+import android.content.Context
+import android.view.DisplayCutout
+import com.android.systemui.R
+import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
+import javax.inject.Inject
+
+/**
+ * Controls [BatteryMeterView.BatteryPercentMode]. It takes into account cutout and qs-qqs
+ * transition fraction when determining the mode.
+ */
+class QsBatteryModeController
+@Inject
+constructor(
+ private val context: Context,
+ private val insetsProvider: StatusBarContentInsetsProvider,
+) {
+
+ private companion object {
+ // MotionLayout frames are in [0, 100]. Where 0 and 100 are reserved for start and end
+ // frames.
+ const val MOTION_LAYOUT_MAX_FRAME = 100
+ // We add a single buffer frame to ensure that battery view faded out completely when we are
+ // about to change it's state
+ const val BUFFER_FRAME_COUNT = 1
+ }
+
+ private var fadeInStartFraction: Float = 0f
+ private var fadeOutCompleteFraction: Float = 0f
+
+ init {
+ updateResources()
+ }
+
+ /**
+ * Returns an appropriate [BatteryMeterView.BatteryPercentMode] for the [qsExpandedFraction] and
+ * [cutout]. We don't show battery estimation in qqs header on the devices with center cutout.
+ * The result might be null when the battery icon is invisible during the qs-qqs transition
+ * animation.
+ */
+ @BatteryMeterView.BatteryPercentMode
+ fun getBatteryMode(cutout: DisplayCutout?, qsExpandedFraction: Float): Int? =
+ when {
+ qsExpandedFraction > fadeInStartFraction -> BatteryMeterView.MODE_ESTIMATE
+ qsExpandedFraction < fadeOutCompleteFraction ->
+ if (hasCenterCutout(cutout)) {
+ BatteryMeterView.MODE_ON
+ } else {
+ BatteryMeterView.MODE_ESTIMATE
+ }
+ else -> null
+ }
+
+ fun updateResources() {
+ fadeInStartFraction =
+ (context.resources.getInteger(R.integer.fade_in_start_frame) - BUFFER_FRAME_COUNT) /
+ MOTION_LAYOUT_MAX_FRAME.toFloat()
+ fadeOutCompleteFraction =
+ (context.resources.getInteger(R.integer.fade_out_complete_frame) + BUFFER_FRAME_COUNT) /
+ MOTION_LAYOUT_MAX_FRAME.toFloat()
+ }
+
+ private fun hasCenterCutout(cutout: DisplayCutout?): Boolean =
+ cutout?.let {
+ !insetsProvider.currentRotationHasCornerCutout() && !it.boundingRectTop.isEmpty
+ }
+ ?: false
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index 91fef1d..ee5f61c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -21,6 +21,7 @@
import android.content.res.XmlResourceParser
import android.graphics.Rect
import android.testing.AndroidTestingRunner
+import android.view.Display
import android.view.DisplayCutout
import android.view.View
import android.view.ViewPropertyAnimator
@@ -77,9 +78,11 @@
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
+import org.mockito.Mockito.same
+import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
private val EMPTY_CHANGES = ConstraintsChanges()
@@ -133,6 +136,7 @@
@Mock
private lateinit var mockedContext: Context
+ private lateinit var viewContext: Context
@Mock(answer = Answers.RETURNS_MOCKS)
private lateinit var view: MotionLayout
@@ -143,6 +147,7 @@
@Mock
private lateinit var largeScreenConstraints: ConstraintSet
@Mock private lateinit var demoModeController: DemoModeController
+ @Mock private lateinit var qsBatteryModeController: QsBatteryModeController
@JvmField @Rule
val mockitoRule = MockitoJUnit.rule()
@@ -175,7 +180,8 @@
.thenReturn(qsCarrierGroupControllerBuilder)
whenever(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
- whenever(view.context).thenReturn(context)
+ viewContext = spy(context)
+ whenever(view.context).thenReturn(viewContext)
whenever(view.resources).thenReturn(context.resources)
whenever(view.setVisibility(ArgumentMatchers.anyInt())).then {
viewVisibility = it.arguments[0] as Int
@@ -192,19 +198,20 @@
setUpMotionLayout(view)
controller = LargeScreenShadeHeaderController(
- view,
- statusBarIconController,
- iconManagerFactory,
- privacyIconsController,
- insetsProvider,
- configurationController,
- variableDateViewControllerFactory,
- batteryMeterViewController,
- dumpManager,
- featureFlags,
- qsCarrierGroupControllerBuilder,
- combinedShadeHeadersConstraintManager,
- demoModeController
+ view,
+ statusBarIconController,
+ iconManagerFactory,
+ privacyIconsController,
+ insetsProvider,
+ configurationController,
+ variableDateViewControllerFactory,
+ batteryMeterViewController,
+ dumpManager,
+ featureFlags,
+ qsCarrierGroupControllerBuilder,
+ combinedShadeHeadersConstraintManager,
+ demoModeController,
+ qsBatteryModeController,
)
whenever(view.isAttachedToWindow).thenReturn(true)
controller.init()
@@ -218,7 +225,6 @@
verify(batteryMeterViewController).init()
verify(batteryMeterViewController).ignoreTunerUpdates()
- verify(batteryMeterView).setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
val inOrder = inOrder(qsCarrierGroupControllerBuilder)
inOrder.verify(qsCarrierGroupControllerBuilder).setQSCarrierGroup(carrierGroup)
@@ -226,6 +232,23 @@
}
@Test
+ fun `battery mode controller called when qsExpandedFraction changes`() {
+ whenever(qsBatteryModeController.getBatteryMode(same(null), eq(0f)))
+ .thenReturn(BatteryMeterView.MODE_ON)
+ whenever(qsBatteryModeController.getBatteryMode(same(null), eq(1f)))
+ .thenReturn(BatteryMeterView.MODE_ESTIMATE)
+ controller.qsVisible = true
+
+ val times = 10
+ repeat(times) {
+ controller.qsExpandedFraction = it / (times - 1).toFloat()
+ }
+
+ verify(batteryMeterView).setPercentShowMode(BatteryMeterView.MODE_ON)
+ verify(batteryMeterView).setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ }
+
+ @Test
fun testClockPivotLtr() {
val width = 200
whenever(clock.width).thenReturn(width)
@@ -684,11 +707,11 @@
configurationController.notifyDensityOrFontScaleChanged()
val captor = ArgumentCaptor.forClass(XmlResourceParser::class.java)
- verify(qqsConstraints).load(eq(context), capture(captor))
+ verify(qqsConstraints).load(eq(viewContext), capture(captor))
assertThat(captor.value.getResId()).isEqualTo(R.xml.qqs_header)
- verify(qsConstraints).load(eq(context), capture(captor))
+ verify(qsConstraints).load(eq(viewContext), capture(captor))
assertThat(captor.value.getResId()).isEqualTo(R.xml.qs_header)
- verify(largeScreenConstraints).load(eq(context), capture(captor))
+ verify(largeScreenConstraints).load(eq(viewContext), capture(captor))
assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header)
}
@@ -786,6 +809,13 @@
whenever(insetsProvider.getStatusBarContentInsetsForCurrentRotation())
.thenReturn(Pair(0, 0).toAndroidPair())
whenever(insetsProvider.currentRotationHasCornerCutout()).thenReturn(false)
+ setupCurrentInsets(null)
+ }
+
+ private fun setupCurrentInsets(cutout: DisplayCutout?) {
+ val mockedDisplay =
+ mock<Display>().also { display -> whenever(display.cutout).thenReturn(cutout) }
+ whenever(viewContext.display).thenReturn(mockedDisplay)
}
private fun<T, U> Pair<T, U>.toAndroidPair(): android.util.Pair<T, U> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
index 2bf2a81..6175df9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
@@ -76,6 +76,7 @@
@Mock private lateinit var mockedContext: Context
@Mock private lateinit var demoModeController: DemoModeController
+ @Mock private lateinit var qsBatteryModeController: QsBatteryModeController
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
var viewVisibility = View.GONE
@@ -130,8 +131,9 @@
featureFlags,
qsCarrierGroupControllerBuilder,
combinedShadeHeadersConstraintManager,
- demoModeController
- )
+ demoModeController,
+ qsBatteryModeController,
+ )
whenever(view.isAttachedToWindow).thenReturn(true)
mLargeScreenShadeHeaderController.init()
carrierIconSlots = listOf(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt
new file mode 100644
index 0000000..b547318
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt
@@ -0,0 +1,100 @@
+package com.android.systemui.shade
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.view.DisplayCutout
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class QsBatteryModeControllerTest : SysuiTestCase() {
+
+ private companion object {
+ val CENTER_TOP_CUTOUT: DisplayCutout =
+ mock<DisplayCutout>().also {
+ whenever(it.boundingRectTop).thenReturn(Rect(10, 0, 20, 10))
+ }
+
+ const val MOTION_LAYOUT_MAX_FRAME = 100
+ const val QQS_START_FRAME = 14
+ const val QS_END_FRAME = 58
+ }
+
+ @JvmField @Rule val mockitoRule = MockitoJUnit.rule()!!
+
+ @Mock private lateinit var insetsProvider: StatusBarContentInsetsProvider
+ @Mock private lateinit var mockedContext: Context
+ @Mock private lateinit var mockedResources: Resources
+
+ private lateinit var controller: QsBatteryModeController // under test
+
+ @Before
+ fun setup() {
+ whenever(mockedContext.resources).thenReturn(mockedResources)
+ whenever(mockedResources.getInteger(R.integer.fade_in_start_frame)).thenReturn(QS_END_FRAME)
+ whenever(mockedResources.getInteger(R.integer.fade_out_complete_frame))
+ .thenReturn(QQS_START_FRAME)
+
+ controller = QsBatteryModeController(mockedContext, insetsProvider)
+ }
+
+ @Test
+ fun `returns MODE_ON for qqs with center cutout`() {
+ assertThat(
+ controller.getBatteryMode(CENTER_TOP_CUTOUT, QQS_START_FRAME.prevFrameToFraction())
+ )
+ .isEqualTo(BatteryMeterView.MODE_ON)
+ }
+
+ @Test
+ fun `returns MODE_ESTIMATE for qs with center cutout`() {
+ assertThat(controller.getBatteryMode(CENTER_TOP_CUTOUT, QS_END_FRAME.nextFrameToFraction()))
+ .isEqualTo(BatteryMeterView.MODE_ESTIMATE)
+ }
+
+ @Test
+ fun `returns MODE_ON for qqs with corner cutout`() {
+ whenever(insetsProvider.currentRotationHasCornerCutout()).thenReturn(true)
+
+ assertThat(
+ controller.getBatteryMode(CENTER_TOP_CUTOUT, QQS_START_FRAME.prevFrameToFraction())
+ )
+ .isEqualTo(BatteryMeterView.MODE_ESTIMATE)
+ }
+
+ @Test
+ fun `returns MODE_ESTIMATE for qs with corner cutout`() {
+ whenever(insetsProvider.currentRotationHasCornerCutout()).thenReturn(true)
+
+ assertThat(controller.getBatteryMode(CENTER_TOP_CUTOUT, QS_END_FRAME.nextFrameToFraction()))
+ .isEqualTo(BatteryMeterView.MODE_ESTIMATE)
+ }
+
+ @Test
+ fun `returns null in-between`() {
+ assertThat(
+ controller.getBatteryMode(CENTER_TOP_CUTOUT, QQS_START_FRAME.nextFrameToFraction())
+ )
+ .isNull()
+ assertThat(controller.getBatteryMode(CENTER_TOP_CUTOUT, QS_END_FRAME.prevFrameToFraction()))
+ .isNull()
+ }
+
+ private fun Int.prevFrameToFraction(): Float = (this - 1) / MOTION_LAYOUT_MAX_FRAME.toFloat()
+ private fun Int.nextFrameToFraction(): Float = (this + 1) / MOTION_LAYOUT_MAX_FRAME.toFloat()
+}