diff options
| author | 2022-09-06 15:46:53 -0400 | |
|---|---|---|
| committer | 2022-09-13 10:19:59 -0400 | |
| commit | c30c4e1412eef93563bef43b456427751af417cf (patch) | |
| tree | c89186660897ad1cac800f834ffb42fd2b6e092a | |
| parent | 9fefe95ba6f566c5993ed84d40e10be533c4cb2e (diff) | |
Fade out/in header with customizer in/out
As the QSPanel doesn't reach the top of the screen, the
LargeScreenShadeHeader peeks out from behind the Edit screen
in certain configurations. In order to hide it, we fade it out/in
together with the circular reveal.
Test: manual
Test: atest NotificationQSContainerControllerTest
Test: atest LargeScreenShadeHeaderControllerTest
Test: atest LargeScreenShadeHeaderControllerCombinedTest
Fixes: 244455245
Change-Id: Ie8b8fb07b28e18b9620a948fbb6589394c4224d6
8 files changed, 130 insertions, 16 deletions
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt index 8bf982d360a1..9c7fbe8842bc 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt @@ -3,7 +3,9 @@ package com.android.systemui.plugins.qs interface QSContainerController { fun setCustomizerAnimating(animating: Boolean) - fun setCustomizerShowing(showing: Boolean) + fun setCustomizerShowing(showing: Boolean) = setCustomizerShowing(showing, 0L) + + fun setCustomizerShowing(showing: Boolean, animationDuration: Long) fun setDetailShowing(showing: Boolean) -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java index b02efba93161..de11d567d858 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java @@ -39,8 +39,15 @@ public class QSDetailClipper { mBackground = (TransitionDrawable) detail.getBackground(); } - public void animateCircularClip(int x, int y, boolean in, AnimatorListener listener) { - updateCircularClip(true /* animate */, x, y, in, listener); + /** + * @param x x position where animation should originate + * @param y y position where animation should originate + * @param in whether animating in or out + * @param listener Animation listener. Called whether or not {@code animate} is true. + * @return the duration of the circular animator + */ + public long animateCircularClip(int x, int y, boolean in, AnimatorListener listener) { + return updateCircularClip(true /* animate */, x, y, in, listener); } /** @@ -50,8 +57,9 @@ public class QSDetailClipper { * @param y y position where animation should originate * @param in whether animating in or out * @param listener Animation listener. Called whether or not {@code animate} is true. + * @return the duration of the circular animator */ - public void updateCircularClip(boolean animate, int x, int y, boolean in, + public long updateCircularClip(boolean animate, int x, int y, boolean in, AnimatorListener listener) { if (mAnimator != null) { mAnimator.cancel(); @@ -87,6 +95,7 @@ public class QSDetailClipper { mAnimator.addListener(mGoneOnEnd); } mAnimator.start(); + return mAnimator.getDuration(); } private final Runnable mReverseBackground = new Runnable() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java index 8ad011904d3d..cf10c7940871 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java @@ -125,9 +125,10 @@ public class QSCustomizer extends LinearLayout { isShown = true; mOpening = true; setVisibility(View.VISIBLE); - mClipper.animateCircularClip(mX, mY, true, new ExpandAnimatorListener(tileAdapter)); + long duration = mClipper.animateCircularClip( + mX, mY, true, new ExpandAnimatorListener(tileAdapter)); mQsContainerController.setCustomizerAnimating(true); - mQsContainerController.setCustomizerShowing(true); + mQsContainerController.setCustomizerShowing(true, duration); } } @@ -153,13 +154,14 @@ public class QSCustomizer extends LinearLayout { // Make sure we're not opening (because we're closing). Nobody can think we are // customizing after the next two lines. mOpening = false; + long duration = 0; if (animate) { - mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener); + duration = mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener); } else { setVisibility(View.GONE); } mQsContainerController.setCustomizerAnimating(animate); - mQsContainerController.setCustomizerShowing(false); + mQsContainerController.setCustomizerShowing(false, duration); } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt index 6b32daff0ab1..fe40d4cbe23a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt @@ -30,6 +30,7 @@ import androidx.constraintlayout.motion.widget.MotionLayout import com.android.settingslib.Utils import com.android.systemui.Dumpable import com.android.systemui.R +import com.android.systemui.animation.Interpolators import com.android.systemui.animation.ShadeInterpolation import com.android.systemui.battery.BatteryMeterView import com.android.systemui.battery.BatteryMeterViewController @@ -310,6 +311,14 @@ class LargeScreenShadeHeaderController @Inject constructor( updateVisibility() } + fun startCustomizingAnimation(show: Boolean, duration: Long) { + header.animate() + .setDuration(duration) + .alpha(if (show) 0f else 1f) + .setInterpolator(if (show) Interpolators.ALPHA_OUT else Interpolators.ALPHA_IN) + .start() + } + private fun loadConstraints() { if (header is MotionLayout) { // Use resources.getXml instead of passing the resource id due to bug b/205018300 diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt index 2a467763951c..d6f0de83ecc1 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt @@ -35,6 +35,7 @@ class NotificationsQSContainerController @Inject constructor( view: NotificationsQuickSettingsContainer, private val navigationModeController: NavigationModeController, private val overviewProxyService: OverviewProxyService, + private val largeScreenShadeHeaderController: LargeScreenShadeHeaderController, private val featureFlags: FeatureFlags, @Main private val delayableExecutor: DelayableExecutor ) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController { @@ -156,9 +157,12 @@ class NotificationsQSContainerController @Inject constructor( } } - override fun setCustomizerShowing(showing: Boolean) { - isQSCustomizing = showing - updateBottomSpacing() + override fun setCustomizerShowing(showing: Boolean, animationDuration: Long) { + if (showing != isQSCustomizing) { + isQSCustomizing = showing + largeScreenShadeHeaderController.startCustomizingAnimation(showing, animationDuration) + updateBottomSpacing() + } } override fun setDetailShowing(showing: Boolean) { 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 e85ffb68de54..c4485389d646 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt @@ -23,6 +23,7 @@ import android.graphics.Rect import android.testing.AndroidTestingRunner import android.view.DisplayCutout import android.view.View +import android.view.ViewPropertyAnimator import android.view.WindowInsets import android.widget.TextView import androidx.constraintlayout.motion.widget.MotionLayout @@ -30,6 +31,7 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.Interpolators import com.android.systemui.animation.ShadeInterpolation import com.android.systemui.battery.BatteryMeterView import com.android.systemui.battery.BatteryMeterViewController @@ -64,6 +66,7 @@ import org.mockito.Answers import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers import org.mockito.Mock +import org.mockito.Mockito import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.anyFloat import org.mockito.Mockito.anyInt @@ -614,6 +617,34 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() { ) } + @Test + fun animateOutOnStartCustomizing() { + val animator = Mockito.mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF) + val duration = 1000L + whenever(view.animate()).thenReturn(animator) + + controller.startCustomizingAnimation(show = true, duration) + + verify(animator).setDuration(duration) + verify(animator).alpha(0f) + verify(animator).setInterpolator(Interpolators.ALPHA_OUT) + verify(animator).start() + } + + @Test + fun animateInOnEndCustomizing() { + val animator = Mockito.mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF) + val duration = 1000L + whenever(view.animate()).thenReturn(animator) + + controller.startCustomizingAnimation(show = false, duration) + + verify(animator).setDuration(duration) + verify(animator).alpha(1f) + verify(animator).setInterpolator(Interpolators.ALPHA_IN) + verify(animator).start() + } + private fun createWindowInsets( topCutout: Rect? = Rect() ): WindowInsets { 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 8511443705e4..5ecfc8eb3649 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt @@ -4,10 +4,12 @@ import android.app.StatusBarManager import android.content.Context import android.testing.AndroidTestingRunner import android.view.View +import android.view.ViewPropertyAnimator import android.widget.TextView import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.Interpolators import com.android.systemui.animation.ShadeInterpolation import com.android.systemui.battery.BatteryMeterView import com.android.systemui.battery.BatteryMeterViewController @@ -29,8 +31,10 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Answers import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock +import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions import org.mockito.Mockito.`when` as whenever @@ -198,4 +202,32 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { context.getString(com.android.internal.R.string.status_bar_alarm_clock) ) } + + @Test + fun animateOutOnStartCustomizing() { + val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF) + val duration = 1000L + whenever(view.animate()).thenReturn(animator) + + mLargeScreenShadeHeaderController.startCustomizingAnimation(show = true, duration) + + verify(animator).setDuration(duration) + verify(animator).alpha(0f) + verify(animator).setInterpolator(Interpolators.ALPHA_OUT) + verify(animator).start() + } + + @Test + fun animateInOnEndCustomizing() { + val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF) + val duration = 1000L + whenever(view.animate()).thenReturn(animator) + + mLargeScreenShadeHeaderController.startCustomizingAnimation(show = false, duration) + + verify(animator).setDuration(duration) + verify(animator).alpha(1f) + verify(animator).setInterpolator(Interpolators.ALPHA_IN) + verify(animator).start() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt index 0c6a6a98052f..12ef036d89d0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt @@ -20,6 +20,7 @@ import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat +import java.util.function.Consumer import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -33,10 +34,10 @@ import org.mockito.Mockito.doNothing import org.mockito.Mockito.eq import org.mockito.Mockito.mock import org.mockito.Mockito.never +import org.mockito.Mockito.reset import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations -import java.util.function.Consumer import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) @@ -63,6 +64,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Mock private lateinit var notificationsQSContainer: NotificationsQuickSettingsContainer @Mock + private lateinit var largeScreenShadeHeaderController: LargeScreenShadeHeaderController + @Mock private lateinit var featureFlags: FeatureFlags @Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener> @@ -92,6 +95,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { notificationsQSContainer, navigationModeController, overviewProxyService, + largeScreenShadeHeaderController, featureFlags, delayableExecutor ) @@ -371,8 +375,14 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { container.removeAllViews() container.addView(newViewWithId(1)) container.addView(newViewWithId(View.NO_ID)) - val controller = NotificationsQSContainerController(container, navigationModeController, - overviewProxyService, featureFlags, delayableExecutor) + val controller = NotificationsQSContainerController( + container, + navigationModeController, + overviewProxyService, + largeScreenShadeHeaderController, + featureFlags, + delayableExecutor + ) controller.updateConstraints() assertThat(container.getChildAt(0).id).isEqualTo(1) @@ -397,6 +407,21 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { verify(notificationsQSContainer).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM) } + @Test + fun testStartCustomizingWithDuration() { + controller.setCustomizerShowing(true, 100L) + verify(largeScreenShadeHeaderController).startCustomizingAnimation(true, 100L) + } + + @Test + fun testEndCustomizingWithDuration() { + controller.setCustomizerShowing(true, 0L) // Only tracks changes + reset(largeScreenShadeHeaderController) + + controller.setCustomizerShowing(false, 100L) + verify(largeScreenShadeHeaderController).startCustomizingAnimation(false, 100L) + } + private fun disableSplitShade() { setSplitShadeEnabled(false) } |