diff options
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) } |