Loading packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt +33 −31 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -145,6 +146,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. Loading Loading @@ -176,14 +185,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() } } } Loading Loading @@ -318,6 +322,7 @@ class LargeScreenShadeHeaderController @Inject constructor( dumpManager.registerDumpable(this) configurationController.addCallback(configurationControllerListener) demoModeController.addCallback(demoModeReceiver) statusBarIconController.addIconGroup(iconManager) } override fun onViewDetached() { Loading @@ -325,6 +330,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) { Loading @@ -339,31 +345,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 Loading Loading @@ -453,7 +438,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 Loading Loading @@ -502,10 +487,8 @@ class LargeScreenShadeHeaderController @Inject constructor( if (visible) { updateSingleCarrier(qsCarrierGroupController.isSingleCarrier) qsCarrierGroupController.setOnSingleCarrierChangedListener { updateSingleCarrier(it) } statusBarIconController.addIconGroup(iconManager) } else { qsCarrierGroupController.setOnSingleCarrierChangedListener(null) statusBarIconController.removeIconGroup(iconManager) } } Loading Loading @@ -578,4 +561,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 } } } } packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt +27 −50 Original line number Diff line number Diff line 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 Loading Loading @@ -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 Loading Loading @@ -158,24 +158,24 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { fun updateListeners_registersWhenVisible() { makeShadeVisible() verify(qsCarrierGroupController).setListening(true) } @Test fun statusIconsAddedWhenAttached() { verify(statusBarIconController).addIconGroup(any()) } @Test fun shadeExpandedFraction_updatesAlpha() { makeShadeVisible() mLargeScreenShadeHeaderController.shadeExpandedFraction = 0.5f verify(view).setAlpha(ShadeInterpolation.getContentAlpha(0.5f)) fun statusIconsRemovedWhenDettached() { mLargeScreenShadeHeaderController.simulateViewDetached() verify(statusBarIconController).removeIconGroup(any()) } @Test fun alphaChangesUpdateVisibility() { fun shadeExpandedFraction_updatesAlpha() { makeShadeVisible() mLargeScreenShadeHeaderController.shadeExpandedFraction = 0f assertThat(viewVisibility).isEqualTo(View.INVISIBLE) mLargeScreenShadeHeaderController.shadeExpandedFraction = 1f assertThat(viewVisibility).isEqualTo(View.VISIBLE) mLargeScreenShadeHeaderController.shadeExpandedFraction = 0.5f verify(view).setAlpha(ShadeInterpolation.getContentAlpha(0.5f)) } @Test Loading Loading @@ -263,54 +263,32 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { } @Test fun testShadeExpanded_true_alpha_zero_invisible() { view.alpha = 0f mLargeScreenShadeHeaderController.largeScreenActive = true mLargeScreenShadeHeaderController.qsVisible = true assertThat(viewVisibility).isEqualTo(View.INVISIBLE) } fun customizerAnimatorChangesViewVisibility() { makeShadeVisible() @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) } @Test fun animatorListenersClearedAtEnd() { val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF) whenever(view.animate()).thenReturn(animator) mLargeScreenShadeHeaderController.startCustomizingAnimation(show = true, 0L) val listenerCaptor = argumentCaptor<Animator.AnimatorListener>() 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()) verify(animator).setListener(null) verify(animator).setUpdateListener(null) assertThat(viewVisibility).isEqualTo(View.VISIBLE) } @Test fun animatorListenersClearedOnCancel() { fun animatorListenerClearedAtEnd() { val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF) whenever(view.animate()).thenReturn(animator) Loading @@ -318,9 +296,8 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { val listenerCaptor = argumentCaptor<Animator.AnimatorListener>() verify(animator).setListener(capture(listenerCaptor)) listenerCaptor.value.onAnimationCancel(mock()) listenerCaptor.value.onAnimationEnd(mock()) verify(animator).setListener(null) verify(animator).setUpdateListener(null) } @Test Loading Loading
packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt +33 −31 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -145,6 +146,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. Loading Loading @@ -176,14 +185,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() } } } Loading Loading @@ -318,6 +322,7 @@ class LargeScreenShadeHeaderController @Inject constructor( dumpManager.registerDumpable(this) configurationController.addCallback(configurationControllerListener) demoModeController.addCallback(demoModeReceiver) statusBarIconController.addIconGroup(iconManager) } override fun onViewDetached() { Loading @@ -325,6 +330,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) { Loading @@ -339,31 +345,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 Loading Loading @@ -453,7 +438,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 Loading Loading @@ -502,10 +487,8 @@ class LargeScreenShadeHeaderController @Inject constructor( if (visible) { updateSingleCarrier(qsCarrierGroupController.isSingleCarrier) qsCarrierGroupController.setOnSingleCarrierChangedListener { updateSingleCarrier(it) } statusBarIconController.addIconGroup(iconManager) } else { qsCarrierGroupController.setOnSingleCarrierChangedListener(null) statusBarIconController.removeIconGroup(iconManager) } } Loading Loading @@ -578,4 +561,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 } } } }
packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt +27 −50 Original line number Diff line number Diff line 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 Loading Loading @@ -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 Loading Loading @@ -158,24 +158,24 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { fun updateListeners_registersWhenVisible() { makeShadeVisible() verify(qsCarrierGroupController).setListening(true) } @Test fun statusIconsAddedWhenAttached() { verify(statusBarIconController).addIconGroup(any()) } @Test fun shadeExpandedFraction_updatesAlpha() { makeShadeVisible() mLargeScreenShadeHeaderController.shadeExpandedFraction = 0.5f verify(view).setAlpha(ShadeInterpolation.getContentAlpha(0.5f)) fun statusIconsRemovedWhenDettached() { mLargeScreenShadeHeaderController.simulateViewDetached() verify(statusBarIconController).removeIconGroup(any()) } @Test fun alphaChangesUpdateVisibility() { fun shadeExpandedFraction_updatesAlpha() { makeShadeVisible() mLargeScreenShadeHeaderController.shadeExpandedFraction = 0f assertThat(viewVisibility).isEqualTo(View.INVISIBLE) mLargeScreenShadeHeaderController.shadeExpandedFraction = 1f assertThat(viewVisibility).isEqualTo(View.VISIBLE) mLargeScreenShadeHeaderController.shadeExpandedFraction = 0.5f verify(view).setAlpha(ShadeInterpolation.getContentAlpha(0.5f)) } @Test Loading Loading @@ -263,54 +263,32 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { } @Test fun testShadeExpanded_true_alpha_zero_invisible() { view.alpha = 0f mLargeScreenShadeHeaderController.largeScreenActive = true mLargeScreenShadeHeaderController.qsVisible = true assertThat(viewVisibility).isEqualTo(View.INVISIBLE) } fun customizerAnimatorChangesViewVisibility() { makeShadeVisible() @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) } @Test fun animatorListenersClearedAtEnd() { val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF) whenever(view.animate()).thenReturn(animator) mLargeScreenShadeHeaderController.startCustomizingAnimation(show = true, 0L) val listenerCaptor = argumentCaptor<Animator.AnimatorListener>() 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()) verify(animator).setListener(null) verify(animator).setUpdateListener(null) assertThat(viewVisibility).isEqualTo(View.VISIBLE) } @Test fun animatorListenersClearedOnCancel() { fun animatorListenerClearedAtEnd() { val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF) whenever(view.animate()).thenReturn(animator) Loading @@ -318,9 +296,8 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { val listenerCaptor = argumentCaptor<Animator.AnimatorListener>() verify(animator).setListener(capture(listenerCaptor)) listenerCaptor.value.onAnimationCancel(mock()) listenerCaptor.value.onAnimationEnd(mock()) verify(animator).setListener(null) verify(animator).setUpdateListener(null) } @Test Loading