Fix performance regressions

* Have the statusIcons always added to StatusBarIconController
  (attach/dettach) instead of when visibility changes. That prevents
  readding views and remeasuring.
* Introduce a specific variable for edit mode to control visibility.
  This makes it so that we don't have to rely on alpha (and it doesn't
  change with QS expansion).

Fixes: 266444213
Fixes: 265104777
Test: perfetto trace
Test: atest sysui-jank-custom
Test: atest LargeScreenShadeHeaderControllerTest
Change-Id: Ib8c3d1bcb1fb59f55c1c97a5b3966a2892b3f4b1
This commit is contained in:
Fabian Kozynski
2023-02-14 14:18:17 -05:00
parent 7bf32dfd3f
commit e65d494a11
2 changed files with 62 additions and 83 deletions

View File

@@ -17,6 +17,7 @@
package com.android.systemui.shade
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.annotation.IdRes
import android.app.StatusBarManager
import android.content.res.Configuration
@@ -144,6 +145,14 @@ class LargeScreenShadeHeaderController @Inject constructor(
updateListeners()
}
private var customizing = false
set(value) {
if (field != value) {
field = value
updateVisibility()
}
}
/**
* Whether the QQS/QS part of the shade is visible. This is particularly important in
* Lockscreen, as the shade is visible but QS is not.
@@ -175,14 +184,9 @@ class LargeScreenShadeHeaderController @Inject constructor(
*/
var shadeExpandedFraction = -1f
set(value) {
if (field != value) {
val oldAlpha = header.alpha
if (qsVisible && field != value) {
header.alpha = ShadeInterpolation.getContentAlpha(value)
field = value
if ((oldAlpha == 0f && header.alpha > 0f) ||
(oldAlpha > 0f && header.alpha == 0f)) {
updateVisibility()
}
}
}
@@ -317,6 +321,7 @@ class LargeScreenShadeHeaderController @Inject constructor(
dumpManager.registerDumpable(this)
configurationController.addCallback(configurationControllerListener)
demoModeController.addCallback(demoModeReceiver)
statusBarIconController.addIconGroup(iconManager)
}
override fun onViewDetached() {
@@ -324,6 +329,7 @@ class LargeScreenShadeHeaderController @Inject constructor(
dumpManager.unregisterDumpable(this::class.java.simpleName)
configurationController.removeCallback(configurationControllerListener)
demoModeController.removeCallback(demoModeReceiver)
statusBarIconController.removeIconGroup(iconManager)
}
fun disable(state1: Int, state2: Int, animate: Boolean) {
@@ -338,31 +344,10 @@ class LargeScreenShadeHeaderController @Inject constructor(
.setDuration(duration)
.alpha(if (show) 0f else 1f)
.setInterpolator(if (show) Interpolators.ALPHA_OUT else Interpolators.ALPHA_IN)
.setUpdateListener {
updateVisibility()
}
.setListener(endAnimationListener)
.setListener(CustomizerAnimationListener(show))
.start()
}
private val endAnimationListener = object : Animator.AnimatorListener {
override fun onAnimationCancel(animation: Animator?) {
clearListeners()
}
override fun onAnimationEnd(animation: Animator?) {
clearListeners()
}
override fun onAnimationRepeat(animation: Animator?) {}
override fun onAnimationStart(animation: Animator?) {}
private fun clearListeners() {
header.animate().setListener(null).setUpdateListener(null)
}
}
private fun loadConstraints() {
if (header is MotionLayout) {
// Use resources.getXml instead of passing the resource id due to bug b/205018300
@@ -443,7 +428,7 @@ class LargeScreenShadeHeaderController @Inject constructor(
private fun updateVisibility() {
val visibility = if (!largeScreenActive && !combinedHeaders || qsDisabled) {
View.GONE
} else if (qsVisible && header.alpha > 0f) {
} else if (qsVisible && !customizing) {
View.VISIBLE
} else {
View.INVISIBLE
@@ -491,10 +476,8 @@ class LargeScreenShadeHeaderController @Inject constructor(
if (visible) {
updateSingleCarrier(qsCarrierGroupController.isSingleCarrier)
qsCarrierGroupController.setOnSingleCarrierChangedListener { updateSingleCarrier(it) }
statusBarIconController.addIconGroup(iconManager)
} else {
qsCarrierGroupController.setOnSingleCarrierChangedListener(null)
statusBarIconController.removeIconGroup(iconManager)
}
}
@@ -566,4 +549,23 @@ class LargeScreenShadeHeaderController @Inject constructor(
@VisibleForTesting
internal fun simulateViewDetached() = this.onViewDetached()
inner class CustomizerAnimationListener(
private val enteringCustomizing: Boolean,
) : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
header.animate().setListener(null)
if (enteringCustomizing) {
customizing = true
}
}
override fun onAnimationStart(animation: Animator?) {
super.onAnimationStart(animation)
if (!enteringCustomizing) {
customizing = false
}
}
}
}

View File

@@ -1,7 +1,6 @@
package com.android.systemui.shade
import android.animation.Animator
import android.animation.ValueAnimator
import android.app.StatusBarManager
import android.content.Context
import android.testing.AndroidTestingRunner
@@ -44,6 +43,7 @@ import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.junit.MockitoJUnit
@@ -156,9 +156,19 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
fun updateListeners_registersWhenVisible() {
makeShadeVisible()
verify(qsCarrierGroupController).setListening(true)
}
@Test
fun statusIconsAddedWhenAttached() {
verify(statusBarIconController).addIconGroup(any())
}
@Test
fun statusIconsRemovedWhenDettached() {
mLargeScreenShadeHeaderController.simulateViewDetached()
verify(statusBarIconController).removeIconGroup(any())
}
@Test
fun shadeExpandedFraction_updatesAlpha() {
makeShadeVisible()
@@ -166,16 +176,6 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
verify(view).setAlpha(ShadeInterpolation.getContentAlpha(0.5f))
}
@Test
fun alphaChangesUpdateVisibility() {
makeShadeVisible()
mLargeScreenShadeHeaderController.shadeExpandedFraction = 0f
assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
mLargeScreenShadeHeaderController.shadeExpandedFraction = 1f
assertThat(viewVisibility).isEqualTo(View.VISIBLE)
}
@Test
fun singleCarrier_enablesCarrierIconsInStatusIcons() {
whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(true)
@@ -261,40 +261,32 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
}
@Test
fun testShadeExpanded_true_alpha_zero_invisible() {
view.alpha = 0f
mLargeScreenShadeHeaderController.largeScreenActive = true
mLargeScreenShadeHeaderController.qsVisible = true
fun customizerAnimatorChangesViewVisibility() {
makeShadeVisible()
assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
}
@Test
fun animatorCallsUpdateVisibilityOnUpdate() {
val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
val duration = 1000L
whenever(view.animate()).thenReturn(animator)
val listenerCaptor = argumentCaptor<Animator.AnimatorListener>()
mLargeScreenShadeHeaderController.startCustomizingAnimation(show = false, 0L)
val updateCaptor = argumentCaptor<ValueAnimator.AnimatorUpdateListener>()
verify(animator).setUpdateListener(capture(updateCaptor))
mLargeScreenShadeHeaderController.largeScreenActive = true
mLargeScreenShadeHeaderController.qsVisible = true
view.alpha = 1f
updateCaptor.value.onAnimationUpdate(mock())
assertThat(viewVisibility).isEqualTo(View.VISIBLE)
view.alpha = 0f
updateCaptor.value.onAnimationUpdate(mock())
mLargeScreenShadeHeaderController.startCustomizingAnimation(show = true, duration)
verify(animator).setListener(capture(listenerCaptor))
// Start and end the animation
listenerCaptor.value.onAnimationStart(mock())
listenerCaptor.value.onAnimationEnd(mock())
assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
reset(animator)
mLargeScreenShadeHeaderController.startCustomizingAnimation(show = false, duration)
verify(animator).setListener(capture(listenerCaptor))
// Start and end the animation
listenerCaptor.value.onAnimationStart(mock())
listenerCaptor.value.onAnimationEnd(mock())
assertThat(viewVisibility).isEqualTo(View.VISIBLE)
}
@Test
fun animatorListenersClearedAtEnd() {
fun animatorListenerClearedAtEnd() {
val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
whenever(view.animate()).thenReturn(animator)
@@ -304,21 +296,6 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
listenerCaptor.value.onAnimationEnd(mock())
verify(animator).setListener(null)
verify(animator).setUpdateListener(null)
}
@Test
fun animatorListenersClearedOnCancel() {
val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
whenever(view.animate()).thenReturn(animator)
mLargeScreenShadeHeaderController.startCustomizingAnimation(show = true, 0L)
val listenerCaptor = argumentCaptor<Animator.AnimatorListener>()
verify(animator).setListener(capture(listenerCaptor))
listenerCaptor.value.onAnimationCancel(mock())
verify(animator).setListener(null)
verify(animator).setUpdateListener(null)
}
@Test