Merge "Fade out/in header with customizer in/out" into tm-qpr-dev

This commit is contained in:
TreeHugger Robot
2022-09-13 21:10:39 +00:00
committed by Android (Google) Code Review
8 changed files with 130 additions and 16 deletions

View File

@@ -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)
}
}

View File

@@ -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() {

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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()
}
}

View File

@@ -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)
}