mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-27 23:36:47 +00:00
Interrupt bubble animation on stash change
This change handles cancelling the currently running bubble animation when the stash state is changing. Demo - http://recall/-/bJtug1HhvXkkeA4MQvIaiP/4jnBgnFaIPez6m7fVLSlf Flag: ACONFIG com.android.wm.shell.enable_bubble_bar DEVELOPMENT Bug: 280605846 Test: atest BubbleBarViewAnimatorTest Change-Id: I34628f8ad741228dd21285ad66e45ef2909fbdab
This commit is contained in:
@@ -172,6 +172,13 @@ public class BubbleBarViewController {
|
||||
}
|
||||
}
|
||||
|
||||
/** Notifies that the stash state is changing. */
|
||||
public void onStashStateChanging() {
|
||||
if (isAnimatingNewBubble()) {
|
||||
mBubbleBarViewAnimator.onStashStateChangingWhileAnimating();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// The below animators are exposed to BubbleStashController so it can manage the stashing
|
||||
// animation.
|
||||
|
||||
@@ -250,6 +250,11 @@ public class BubbleStashController {
|
||||
&& !mBubblesShowingOnHome
|
||||
&& !mBubblesShowingOnOverview;
|
||||
if (mIsStashed != isStashed) {
|
||||
// notify the view controller that the stash state is about to change so that it can
|
||||
// cancel an ongoing animation if there is one.
|
||||
// note that this has to be called before updating mIsStashed with the new value,
|
||||
// otherwise interrupting an ongoing animation may update it again with the wrong state
|
||||
mBarViewController.onStashStateChanging();
|
||||
mIsStashed = isStashed;
|
||||
if (mAnimator != null) {
|
||||
mAnimator.cancel();
|
||||
@@ -423,4 +428,29 @@ public class BubbleStashController {
|
||||
mIsStashed = true;
|
||||
onIsStashedChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the values of the internal animators after the new bubble animation was interrupted
|
||||
*
|
||||
* @param isStashed whether the current state should be stashed
|
||||
* @param bubbleBarTranslationY the current bubble bar translation. this is only used if the
|
||||
* bubble bar is showing to ensure that the stash animator runs
|
||||
* smoothly.
|
||||
*/
|
||||
public void onNewBubbleAnimationInterrupted(boolean isStashed, float bubbleBarTranslationY) {
|
||||
if (isStashed) {
|
||||
mBubbleStashedHandleAlpha.setValue(1);
|
||||
mIconAlphaForStash.setValue(0);
|
||||
mIconScaleForStash.updateValue(STASHED_BAR_SCALE);
|
||||
mIconTranslationYForStash.updateValue(getStashTranslation());
|
||||
} else {
|
||||
mBubbleStashedHandleAlpha.setValue(0);
|
||||
mHandleViewController.setTranslationYForSwipe(0);
|
||||
mIconAlphaForStash.setValue(1);
|
||||
mIconScaleForStash.updateValue(1);
|
||||
mIconTranslationYForStash.updateValue(bubbleBarTranslationY);
|
||||
}
|
||||
mIsStashed = isStashed;
|
||||
onIsStashedChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,17 @@ constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
animator.addEndListener { _, _, _, _, _, _, _ ->
|
||||
animator.addEndListener { _, _, _, canceled, _, _, _ ->
|
||||
// if the show animation was canceled, also cancel the hide animation. this is typically
|
||||
// canceled in this class, but could potentially be canceled elsewhere.
|
||||
if (canceled) {
|
||||
val hideAnimation = animatingBubble?.hideAnimation ?: return@addEndListener
|
||||
scheduler.cancel(hideAnimation)
|
||||
animatingBubble = null
|
||||
bubbleBarView.onAnimatingBubbleCompleted()
|
||||
bubbleBarView.relativePivotY = 1f
|
||||
return@addEndListener
|
||||
}
|
||||
// the bubble bar is now fully settled in. update taskbar touch region so it's touchable
|
||||
bubbleStashController.updateTaskbarTouchRegion()
|
||||
}
|
||||
@@ -200,6 +210,7 @@ constructor(
|
||||
* 3. The third part is the overshoot. The handle is made fully visible.
|
||||
*/
|
||||
private fun buildHideAnimation() = Runnable {
|
||||
if (animatingBubble == null) return@Runnable
|
||||
val offset = bubbleStashController.diffBetweenHandleAndBarCenters
|
||||
val stashedHandleTranslationY =
|
||||
bubbleStashController.stashedHandleTranslationForNewBubbleAnimation
|
||||
@@ -238,9 +249,9 @@ constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
animator.addEndListener { _, _, _, _, _, _, _ ->
|
||||
animator.addEndListener { _, _, _, canceled, _, _, _ ->
|
||||
animatingBubble = null
|
||||
bubbleStashController.stashBubbleBarImmediate()
|
||||
if (!canceled) bubbleStashController.stashBubbleBarImmediate()
|
||||
bubbleBarView.onAnimatingBubbleCompleted()
|
||||
bubbleBarView.relativePivotY = 1f
|
||||
bubbleStashController.updateTaskbarTouchRegion()
|
||||
@@ -256,4 +267,18 @@ constructor(
|
||||
bubbleBarView.relativePivotY = 1f
|
||||
animatingBubble = null
|
||||
}
|
||||
|
||||
/** Notifies the animator that the taskbar area was touched during an animation. */
|
||||
fun onStashStateChangingWhileAnimating() {
|
||||
val hideAnimation = animatingBubble?.hideAnimation ?: return
|
||||
scheduler.cancel(hideAnimation)
|
||||
animatingBubble = null
|
||||
bubbleStashController.stashedHandlePhysicsAnimator.cancel()
|
||||
bubbleBarView.onAnimatingBubbleCompleted()
|
||||
bubbleBarView.relativePivotY = 1f
|
||||
bubbleStashController.onNewBubbleAnimationInterrupted(
|
||||
/* isStashed= */ bubbleBarView.alpha == 0f,
|
||||
bubbleBarView.translationY
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.verify
|
||||
import org.mockito.kotlin.whenever
|
||||
@@ -52,47 +53,23 @@ import org.mockito.kotlin.whenever
|
||||
class BubbleBarViewAnimatorTest {
|
||||
|
||||
private val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
private val animatorScheduler = TestBubbleBarViewAnimatorScheduler()
|
||||
private lateinit var animatorScheduler: TestBubbleBarViewAnimatorScheduler
|
||||
private lateinit var overflowView: BubbleView
|
||||
private lateinit var bubbleView: BubbleView
|
||||
private lateinit var bubble: BubbleBarBubble
|
||||
private lateinit var bubbleBarView: BubbleBarView
|
||||
private lateinit var bubbleStashController: BubbleStashController
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
animatorScheduler = TestBubbleBarViewAnimatorScheduler()
|
||||
PhysicsAnimatorTestUtils.prepareForTest()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animateBubbleInForStashed() {
|
||||
lateinit var overflowView: BubbleView
|
||||
lateinit var bubbleView: BubbleView
|
||||
lateinit var bubble: BubbleBarBubble
|
||||
val bubbleBarView = BubbleBarView(context)
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {
|
||||
bubbleBarView.layoutParams = FrameLayout.LayoutParams(0, 0)
|
||||
val inflater = LayoutInflater.from(context)
|
||||
|
||||
val bitmap = ColorDrawable(Color.WHITE).toBitmap(width = 20, height = 20)
|
||||
overflowView =
|
||||
inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
|
||||
overflowView.setOverflow(BubbleBarOverflow(overflowView), bitmap)
|
||||
bubbleBarView.addView(overflowView)
|
||||
|
||||
val bubbleInfo = BubbleInfo("key", 0, null, null, 0, context.packageName, null, false)
|
||||
bubbleView =
|
||||
inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
|
||||
bubble =
|
||||
BubbleBarBubble(bubbleInfo, bubbleView, bitmap, bitmap, Color.WHITE, Path(), "")
|
||||
bubbleView.setBubble(bubble)
|
||||
bubbleBarView.addView(bubbleView)
|
||||
}
|
||||
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
|
||||
|
||||
val bubbleStashController = mock<BubbleStashController>()
|
||||
whenever(bubbleStashController.isStashed).thenReturn(true)
|
||||
whenever(bubbleStashController.diffBetweenHandleAndBarCenters)
|
||||
.thenReturn(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS)
|
||||
whenever(bubbleStashController.stashedHandleTranslationForNewBubbleAnimation)
|
||||
.thenReturn(HANDLE_TRANSLATION)
|
||||
whenever(bubbleStashController.bubbleBarTranslationYForTaskbar)
|
||||
.thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
|
||||
setUpBubbleBar()
|
||||
setUpBubbleStashController()
|
||||
|
||||
val handle = View(context)
|
||||
val handleAnimator = PhysicsAnimator.getInstance(handle)
|
||||
@@ -106,7 +83,7 @@ class BubbleBarViewAnimatorTest {
|
||||
}
|
||||
|
||||
// let the animation start and wait for it to complete
|
||||
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {}
|
||||
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
|
||||
|
||||
assertThat(handle.alpha).isEqualTo(0)
|
||||
@@ -123,7 +100,7 @@ class BubbleBarViewAnimatorTest {
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)
|
||||
|
||||
// let the animation start and wait for it to complete
|
||||
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {}
|
||||
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
|
||||
|
||||
assertThat(handle.alpha).isEqualTo(1)
|
||||
@@ -135,38 +112,8 @@ class BubbleBarViewAnimatorTest {
|
||||
|
||||
@Test
|
||||
fun animateBubbleInForStashed_tapAnimatingBubble() {
|
||||
lateinit var overflowView: BubbleView
|
||||
lateinit var bubbleView: BubbleView
|
||||
lateinit var bubble: BubbleBarBubble
|
||||
val bubbleBarView = BubbleBarView(context)
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {
|
||||
bubbleBarView.layoutParams = FrameLayout.LayoutParams(0, 0)
|
||||
val inflater = LayoutInflater.from(context)
|
||||
|
||||
val bitmap = ColorDrawable(Color.WHITE).toBitmap(width = 20, height = 20)
|
||||
overflowView =
|
||||
inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
|
||||
overflowView.setOverflow(BubbleBarOverflow(overflowView), bitmap)
|
||||
bubbleBarView.addView(overflowView)
|
||||
|
||||
val bubbleInfo = BubbleInfo("key", 0, null, null, 0, context.packageName, null, false)
|
||||
bubbleView =
|
||||
inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
|
||||
bubble =
|
||||
BubbleBarBubble(bubbleInfo, bubbleView, bitmap, bitmap, Color.WHITE, Path(), "")
|
||||
bubbleView.setBubble(bubble)
|
||||
bubbleBarView.addView(bubbleView)
|
||||
}
|
||||
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
|
||||
|
||||
val bubbleStashController = mock<BubbleStashController>()
|
||||
whenever(bubbleStashController.isStashed).thenReturn(true)
|
||||
whenever(bubbleStashController.diffBetweenHandleAndBarCenters)
|
||||
.thenReturn(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS)
|
||||
whenever(bubbleStashController.stashedHandleTranslationForNewBubbleAnimation)
|
||||
.thenReturn(HANDLE_TRANSLATION)
|
||||
whenever(bubbleStashController.bubbleBarTranslationYForTaskbar)
|
||||
.thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
|
||||
setUpBubbleBar()
|
||||
setUpBubbleStashController()
|
||||
|
||||
val handle = View(context)
|
||||
val handleAnimator = PhysicsAnimator.getInstance(handle)
|
||||
@@ -180,7 +127,7 @@ class BubbleBarViewAnimatorTest {
|
||||
}
|
||||
|
||||
// let the animation start and wait for it to complete
|
||||
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {}
|
||||
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
|
||||
|
||||
assertThat(handle.alpha).isEqualTo(0)
|
||||
@@ -206,6 +153,151 @@ class BubbleBarViewAnimatorTest {
|
||||
assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animateBubbleInForStashed_touchTaskbarArea_whileShowing() {
|
||||
setUpBubbleBar()
|
||||
setUpBubbleStashController()
|
||||
|
||||
val handle = View(context)
|
||||
val handleAnimator = PhysicsAnimator.getInstance(handle)
|
||||
whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator)
|
||||
|
||||
val animator =
|
||||
BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
|
||||
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {
|
||||
animator.animateBubbleInForStashed(bubble)
|
||||
}
|
||||
|
||||
// wait for the animation to start
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {}
|
||||
PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(handleAnimator) { true }
|
||||
|
||||
assertThat(handleAnimator.isRunning()).isTrue()
|
||||
assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
|
||||
// verify the hide bubble animation is pending
|
||||
assertThat(animatorScheduler.delayedBlock).isNotNull()
|
||||
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {
|
||||
animator.onStashStateChangingWhileAnimating()
|
||||
}
|
||||
|
||||
// verify that the hide animation was canceled
|
||||
assertThat(animatorScheduler.delayedBlock).isNull()
|
||||
assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
|
||||
verify(bubbleStashController).onNewBubbleAnimationInterrupted(any(), any())
|
||||
|
||||
// PhysicsAnimatorTestUtils posts the cancellation to the main thread so we need to wait
|
||||
// again
|
||||
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
|
||||
assertThat(handleAnimator.isRunning()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animateBubbleInForStashed_touchTaskbarArea_whileHiding() {
|
||||
setUpBubbleBar()
|
||||
setUpBubbleStashController()
|
||||
|
||||
val handle = View(context)
|
||||
val handleAnimator = PhysicsAnimator.getInstance(handle)
|
||||
whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator)
|
||||
|
||||
val animator =
|
||||
BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
|
||||
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {
|
||||
animator.animateBubbleInForStashed(bubble)
|
||||
}
|
||||
|
||||
// let the animation start and wait for it to complete
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {}
|
||||
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
|
||||
|
||||
// execute the hide bubble animation
|
||||
assertThat(animatorScheduler.delayedBlock).isNotNull()
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)
|
||||
|
||||
// wait for the hide animation to start
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {}
|
||||
assertThat(handleAnimator.isRunning()).isTrue()
|
||||
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {
|
||||
animator.onStashStateChangingWhileAnimating()
|
||||
}
|
||||
|
||||
assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
|
||||
verify(bubbleStashController).onNewBubbleAnimationInterrupted(any(), any())
|
||||
|
||||
// PhysicsAnimatorTestUtils posts the cancellation to the main thread so we need to wait
|
||||
// again
|
||||
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
|
||||
assertThat(handleAnimator.isRunning()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animateBubbleInForStashed_showAnimationCanceled() {
|
||||
setUpBubbleBar()
|
||||
setUpBubbleStashController()
|
||||
|
||||
val handle = View(context)
|
||||
val handleAnimator = PhysicsAnimator.getInstance(handle)
|
||||
whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator)
|
||||
|
||||
val animator =
|
||||
BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
|
||||
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {
|
||||
animator.animateBubbleInForStashed(bubble)
|
||||
}
|
||||
|
||||
// wait for the animation to start
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {}
|
||||
PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(handleAnimator) { true }
|
||||
|
||||
assertThat(handleAnimator.isRunning()).isTrue()
|
||||
assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
|
||||
assertThat(animatorScheduler.delayedBlock).isNotNull()
|
||||
|
||||
handleAnimator.cancel()
|
||||
assertThat(handleAnimator.isRunning()).isFalse()
|
||||
assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
|
||||
assertThat(animatorScheduler.delayedBlock).isNull()
|
||||
}
|
||||
|
||||
private fun setUpBubbleBar() {
|
||||
bubbleBarView = BubbleBarView(context)
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {
|
||||
bubbleBarView.layoutParams = FrameLayout.LayoutParams(0, 0)
|
||||
val inflater = LayoutInflater.from(context)
|
||||
|
||||
val bitmap = ColorDrawable(Color.WHITE).toBitmap(width = 20, height = 20)
|
||||
overflowView =
|
||||
inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
|
||||
overflowView.setOverflow(BubbleBarOverflow(overflowView), bitmap)
|
||||
bubbleBarView.addView(overflowView)
|
||||
|
||||
val bubbleInfo = BubbleInfo("key", 0, null, null, 0, context.packageName, null, false)
|
||||
bubbleView =
|
||||
inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
|
||||
bubble =
|
||||
BubbleBarBubble(bubbleInfo, bubbleView, bitmap, bitmap, Color.WHITE, Path(), "")
|
||||
bubbleView.setBubble(bubble)
|
||||
bubbleBarView.addView(bubbleView)
|
||||
}
|
||||
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
|
||||
}
|
||||
|
||||
private fun setUpBubbleStashController() {
|
||||
bubbleStashController = mock<BubbleStashController>()
|
||||
whenever(bubbleStashController.isStashed).thenReturn(true)
|
||||
whenever(bubbleStashController.diffBetweenHandleAndBarCenters)
|
||||
.thenReturn(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS)
|
||||
whenever(bubbleStashController.stashedHandleTranslationForNewBubbleAnimation)
|
||||
.thenReturn(HANDLE_TRANSLATION)
|
||||
whenever(bubbleStashController.bubbleBarTranslationYForTaskbar)
|
||||
.thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
|
||||
}
|
||||
|
||||
private class TestBubbleBarViewAnimatorScheduler : BubbleBarViewAnimator.Scheduler {
|
||||
|
||||
var delayedBlock: Runnable? = null
|
||||
|
||||
Reference in New Issue
Block a user