From a5cd1a0dbbace287716020a71f7366f70d711baa Mon Sep 17 00:00:00 2001 From: mpodolian Date: Wed, 24 Jul 2024 00:01:51 +0100 Subject: [PATCH] Added TransientTaskbarStashController implementation. Added TransientTaskbarStashController implementation of BubbleStashController interface. Added tests for the implementation. Bug: 346391377 Flag: com.android.wm.shell.enable_bubble_bar Test: TransientTaskbarStashController Change-Id: Ie302086d6b4aac48a948473908b68c2c436e4b30 --- .../bubbles/stashing/BubbleStashController.kt | 25 +- ....kt => PersistentBubbleStashController.kt} | 39 +- .../TransientBubbleStashController.kt | 373 ++++++++++++++++++ ...=> PersistentBubbleStashControllerTest.kt} | 10 +- .../bubbles/stashing/StashingTestUtils.kt | 15 +- .../TransientBubbleStashControllerTest.kt | 336 ++++++++++++++++ 6 files changed, 753 insertions(+), 45 deletions(-) rename quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/{PersistentTaskbarStashController.kt => PersistentBubbleStashController.kt} (94%) create mode 100644 quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt rename quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/{PersistentTaskbarStashControllerTest.kt => PersistentBubbleStashControllerTest.kt} (98%) create mode 100644 quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt index e42b6d6769..59fc76c48f 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt @@ -81,12 +81,7 @@ interface BubbleStashController { controllersAfterInitAction: ControllersAfterInitAction ) - /** Sets stashed and expanded state of the bubble bar */ - fun updateStashedAndExpandedState(stash: Boolean = false, expand: Boolean = false) - - /** - * Shows the bubble bar at [getBubbleBarTranslationY] position immediately without animation. - */ + /** Shows the bubble bar at [bubbleBarTranslationY] position immediately without animation. */ fun showBubbleBarImmediate() /** Shows the bubble bar at [bubbleBarTranslationY] position immediately without animation. */ @@ -120,21 +115,17 @@ interface BubbleStashController { * Stashes the bubble bar (transform to the handle view), or just shrink width of the expanded * bubble bar based on the controller implementation. */ - fun stashBubbleBar() { - updateStashedAndExpandedState(stash = true, expand = false) - } + fun stashBubbleBar() /** Shows the bubble bar, and expands bubbles depending on [expandBubbles]. */ - fun showBubbleBar(expandBubbles: Boolean) { - updateStashedAndExpandedState(stash = false, expandBubbles) - } + fun showBubbleBar(expandBubbles: Boolean) // TODO(b/354218264): Move to BubbleBarViewAnimator /** * The difference on the Y axis between the center of the handle and the center of the bubble * bar. */ - fun getSlideInAnimationDistanceY(): Float + fun getDiffBetweenHandleAndBarCenters(): Float // TODO(b/354218264): Move to BubbleBarViewAnimator /** The distance the handle moves as part of the new bubble animation. */ @@ -190,13 +181,5 @@ interface BubbleStashController { /** The scale bubble bar animates to when being stashed. */ const val STASHED_BAR_SCALE = 0.5f - - /** Creates new instance of [BubbleStashController] */ - @JvmStatic - fun newInstance( - taskbarHotseatDimensionsProvider: TaskbarHotseatDimensionsProvider - ): BubbleStashController { - return PersistentTaskbarStashController(taskbarHotseatDimensionsProvider) - } } } diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentTaskbarStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt similarity index 94% rename from quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentTaskbarStashController.kt rename to quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt index 4a05a5ebfc..62fe221c70 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentTaskbarStashController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt @@ -33,7 +33,7 @@ import com.android.launcher3.util.MultiPropertyFactory import com.android.wm.shell.common.bubbles.BubbleBarLocation import com.android.wm.shell.shared.animation.PhysicsAnimator -class PersistentTaskbarStashController( +class PersistentBubbleStashController( private val taskbarHotseatDimensionsProvider: TaskbarHotseatDimensionsProvider, ) : BubbleStashController { @@ -54,7 +54,7 @@ class PersistentTaskbarStashController( } if (onHome) { // When transition to home we should show collapse the bubble bar - updateStashedAndExpandedState(stash = false, expand = false) + updateExpandedState(expand = false) } animateBubbleBarY() bubbleBarViewController.onBubbleBarConfigurationChanged(/* animate= */ true) @@ -66,7 +66,7 @@ class PersistentTaskbarStashController( field = onOverview if (!onOverview) { // When transition from overview we should show collapse the bubble bar - updateStashedAndExpandedState(stash = false, expand = false) + updateExpandedState(expand = false) } bubbleBarViewController.onBubbleBarConfigurationChanged(/* animate= */ true) } @@ -114,7 +114,8 @@ class PersistentTaskbarStashController( this.bubbleBarViewController = bubbleBarViewController this.controllersAfterInitAction = controllersAfterInitAction bubbleBarTranslationYAnimator = bubbleBarViewController.bubbleBarTranslationY - bubbleBarAlphaAnimator = bubbleBarViewController.bubbleBarAlpha.get(0) + // bubble bar has only alpha property, getting it at index 0 + bubbleBarAlphaAnimator = bubbleBarViewController.bubbleBarAlpha.get(/* index= */ 0) bubbleBarScaleAnimator = bubbleBarViewController.bubbleBarScale } @@ -131,16 +132,6 @@ class PersistentTaskbarStashController( animatorSet.setDuration(BAR_STASH_DURATION).start() } - override fun updateStashedAndExpandedState(stash: Boolean, expand: Boolean) { - if (bubbleBarViewController.isHiddenForNoBubbles) { - // If there are no bubbles the bar is invisible, nothing to do here. - return - } - if (bubbleBarViewController.isExpanded != expand) { - bubbleBarViewController.isExpanded = expand - } - } - override fun showBubbleBarImmediate() = showBubbleBarImmediate(bubbleBarTranslationY) override fun showBubbleBarImmediate(bubbleBarTranslationY: Float) { @@ -154,6 +145,14 @@ class PersistentTaskbarStashController( // operation is performed. } + override fun stashBubbleBar() { + updateExpandedState(expand = false) + } + + override fun showBubbleBar(expandBubbles: Boolean) { + updateExpandedState(expandBubbles) + } + override fun stashBubbleBarImmediate() { // When the bubble bar is shown for the persistent task bar, there is no handle view, so no // operation is performed. @@ -176,7 +175,7 @@ class PersistentTaskbarStashController( override fun isEventOverBubbleBarViews(ev: MotionEvent): Boolean = bubbleBarViewController.isEventOverAnyItem(ev) - override fun getSlideInAnimationDistanceY(): Float { + override fun getDiffBetweenHandleAndBarCenters(): Float { // distance from the bottom of the screen and the bubble bar center. return -bubbleBarViewController.bubbleBarCollapsedHeight / 2f } @@ -199,6 +198,16 @@ class PersistentTaskbarStashController( // no op since does not have a handle view } + private fun updateExpandedState(expand: Boolean) { + if (bubbleBarViewController.isHiddenForNoBubbles) { + // If there are no bubbles the bar is invisible, nothing to do here. + return + } + if (bubbleBarViewController.isExpanded != expand) { + bubbleBarViewController.isExpanded = expand + } + } + /** Animates bubble bar Y accordingly to the showing mode */ private fun animateBubbleBarY() { val animator = diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt new file mode 100644 index 0000000000..23e009b3a1 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar.bubbles.stashing + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.AnimatorSet +import android.content.res.Resources +import android.view.MotionEvent +import android.view.View +import androidx.annotation.VisibleForTesting +import com.android.launcher3.R +import com.android.launcher3.anim.AnimatedFloat +import com.android.launcher3.taskbar.StashedHandleViewController +import com.android.launcher3.taskbar.TaskbarInsetsController +import com.android.launcher3.taskbar.bubbles.BubbleBarViewController +import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController +import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_STASH_DURATION +import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_TRANSLATION_DURATION +import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.STASHED_BAR_SCALE +import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.ControllersAfterInitAction +import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.TaskbarHotseatDimensionsProvider +import com.android.launcher3.util.MultiPropertyFactory +import com.android.wm.shell.common.bubbles.BubbleBarLocation +import com.android.wm.shell.shared.animation.PhysicsAnimator + +class TransientBubbleStashController( + private val taskbarHotseatDimensionsProvider: TaskbarHotseatDimensionsProvider, + resources: Resources +) : BubbleStashController { + + private lateinit var bubbleBarViewController: BubbleBarViewController + private lateinit var taskbarInsetsController: TaskbarInsetsController + private lateinit var controllersAfterInitAction: ControllersAfterInitAction + + // stash view properties + private var bubbleStashedHandleViewController: BubbleStashedHandleViewController? = null + private var stashHandleViewAlpha: MultiPropertyFactory.MultiProperty? = null + private var stashedHeight: Int = 0 + + // bubble bar properties + private lateinit var bubbleBarAlpha: MultiPropertyFactory.MultiProperty + private lateinit var bubbleBarTranslationYAnimator: AnimatedFloat + private lateinit var bubbleBarScale: AnimatedFloat + private val mHandleCenterFromScreenBottom = + resources.getDimensionPixelSize(R.dimen.bubblebar_stashed_size) / 2f + + private var animator: AnimatorSet? = null + + override var isStashed: Boolean = false + @VisibleForTesting set + + override var isBubblesShowingOnHome: Boolean = false + set(onHome) { + if (field == onHome) return + field = onHome + if (!bubbleBarViewController.hasBubbles()) { + // if there are no bubbles, there's nothing to show, so just return. + return + } + if (onHome) { + updateStashedAndExpandedState(stash = false, expand = false) + // When transitioning from app to home we need to animate the bubble bar + // here to align with hotseat center. + animateBubbleBarYToHotseat() + } else if (!bubbleBarViewController.isExpanded) { + updateStashedAndExpandedState(stash = true, expand = false) + } + bubbleBarViewController.onBubbleBarConfigurationChanged(/* animate= */ true) + } + + override var isBubblesShowingOnOverview: Boolean = false + set(onOverview) { + if (field == onOverview) return + field = onOverview + if (onOverview) { + // When transitioning to overview we need to animate the bubble bar to align with + // the taskbar bottom. + animateBubbleBarYToTaskbar() + } else { + updateStashedAndExpandedState(stash = true, expand = false) + } + bubbleBarViewController.onBubbleBarConfigurationChanged(/* animate= */ true) + } + + override var isSysuiLocked: Boolean = false + set(isLocked) { + if (field == isLocked) return + field = isLocked + if (!isLocked && bubbleBarViewController.hasBubbles()) { + animateAfterUnlock() + } + } + + override val isTransientTaskBar: Boolean = true + + override val bubbleBarTranslationYForHotseat: Float + get() { + val hotseatBottomSpace = taskbarHotseatDimensionsProvider.getHotseatBottomSpace() + val hotseatCellHeight = taskbarHotseatDimensionsProvider.getHotseatHeight() + val bubbleBarHeight: Float = bubbleBarViewController.bubbleBarCollapsedHeight + return -hotseatBottomSpace - (hotseatCellHeight - bubbleBarHeight) / 2 + } + + override val bubbleBarTranslationYForTaskbar: Float = + -taskbarHotseatDimensionsProvider.getTaskbarBottomSpace().toFloat() + + /** Check if we have handle view controller */ + override val hasHandleView: Boolean + get() = bubbleStashedHandleViewController != null + + override fun init( + taskbarInsetsController: TaskbarInsetsController, + bubbleBarViewController: BubbleBarViewController, + bubbleStashedHandleViewController: BubbleStashedHandleViewController?, + controllersAfterInitAction: ControllersAfterInitAction + ) { + this.taskbarInsetsController = taskbarInsetsController + this.bubbleBarViewController = bubbleBarViewController + this.bubbleStashedHandleViewController = bubbleStashedHandleViewController + this.controllersAfterInitAction = controllersAfterInitAction + bubbleBarTranslationYAnimator = bubbleBarViewController.bubbleBarTranslationY + // bubble bar has only alpha property, getting it at index 0 + bubbleBarAlpha = bubbleBarViewController.bubbleBarAlpha.get(/* index= */ 0) + bubbleBarScale = bubbleBarViewController.bubbleBarScale + stashedHeight = bubbleStashedHandleViewController?.stashedHeight ?: 0 + stashHandleViewAlpha = + bubbleStashedHandleViewController + ?.stashedHandleAlpha + ?.get(StashedHandleViewController.ALPHA_INDEX_STASHED) + } + + private fun animateAfterUnlock() { + val animatorSet = AnimatorSet() + if (isBubblesShowingOnHome || isBubblesShowingOnOverview) { + isStashed = false + animatorSet.playTogether( + bubbleBarScale.animateToValue(1f), + bubbleBarTranslationYAnimator.animateToValue(bubbleBarTranslationY), + bubbleBarAlpha.animateToValue(1f) + ) + } else { + isStashed = true + stashHandleViewAlpha?.let { animatorSet.playTogether(it.animateToValue(1f)) } + } + animatorSet.updateTouchRegionOnAnimationEnd().setDuration(BAR_STASH_DURATION).start() + } + + override fun showBubbleBarImmediate() { + showBubbleBarImmediate(bubbleBarTranslationY) + } + + override fun showBubbleBarImmediate(bubbleBarTranslationY: Float) { + bubbleStashedHandleViewController?.setTranslationYForSwipe(0f) + stashHandleViewAlpha?.value = 0f + this.bubbleBarTranslationYAnimator.updateValue(bubbleBarTranslationY) + bubbleBarAlpha.setValue(1f) + bubbleBarScale.updateValue(1f) + isStashed = false + onIsStashedChanged() + } + + override fun stashBubbleBarImmediate() { + bubbleStashedHandleViewController?.setTranslationYForSwipe(0f) + stashHandleViewAlpha?.value = 1f + this.bubbleBarTranslationYAnimator.updateValue(getStashTranslation()) + bubbleBarAlpha.setValue(0f) + bubbleBarScale.updateValue(STASHED_BAR_SCALE) + isStashed = true + onIsStashedChanged() + } + + override fun getTouchableHeight(): Int = + when { + isStashed -> stashedHeight + isBubbleBarVisible() -> bubbleBarViewController.bubbleBarCollapsedHeight.toInt() + else -> 0 + } + + override fun isBubbleBarVisible(): Boolean = bubbleBarViewController.hasBubbles() && !isStashed + + override fun onNewBubbleAnimationInterrupted(isStashed: Boolean, bubbleBarTranslationY: Float) = + if (isStashed) { + stashBubbleBarImmediate() + } else { + showBubbleBarImmediate(bubbleBarTranslationY) + } + + /** Check if [ev] belongs to the stash handle or the bubble bar views. */ + override fun isEventOverBubbleBarViews(ev: MotionEvent): Boolean { + val isOverHandle = bubbleStashedHandleViewController?.isEventOverHandle(ev) ?: false + return isOverHandle || bubbleBarViewController.isEventOverAnyItem(ev) + } + + /** Set the bubble bar stash handle location . */ + override fun setBubbleBarLocation(bubbleBarLocation: BubbleBarLocation) { + bubbleStashedHandleViewController?.setBubbleBarLocation(bubbleBarLocation) + } + + override fun stashBubbleBar() { + updateStashedAndExpandedState(stash = true, expand = false) + } + + override fun showBubbleBar(expandBubbles: Boolean) { + updateStashedAndExpandedState(stash = false, expandBubbles) + } + + override fun getDiffBetweenHandleAndBarCenters(): Float { + // the difference between the centers of the handle and the bubble bar is the difference + // between their distance from the bottom of the screen. + val barCenter: Float = bubbleBarViewController.bubbleBarCollapsedHeight / 2f + return mHandleCenterFromScreenBottom - barCenter + } + + override fun getStashedHandleTranslationForNewBubbleAnimation(): Float { + return -mHandleCenterFromScreenBottom + } + + override fun getStashedHandlePhysicsAnimator(): PhysicsAnimator? { + return bubbleStashedHandleViewController?.physicsAnimator + } + + override fun updateTaskbarTouchRegion() { + taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged() + } + + override fun setHandleTranslationY(translationY: Float) { + bubbleStashedHandleViewController?.setTranslationYForSwipe(translationY) + } + + private fun getStashTranslation(): Float { + return (bubbleBarViewController.bubbleBarCollapsedHeight - stashedHeight) / 2f + } + + /** + * Create a stash animation. + * + * @param isStashed whether it's a stash animation or an unstash animation + * @param duration duration of the animation + * @return the animation + */ + @Suppress("SameParameterValue") + private fun createStashAnimator(isStashed: Boolean, duration: Long): AnimatorSet { + val animatorSet = AnimatorSet() + val fullLengthAnimatorSet = AnimatorSet() + // Not exactly half and may overlap. See [first|second]HalfDurationScale below. + val firstHalfAnimatorSet = AnimatorSet() + val secondHalfAnimatorSet = AnimatorSet() + val firstHalfDurationScale: Float + val secondHalfDurationScale: Float + val stashHandleAlphaValue: Float + if (isStashed) { + firstHalfDurationScale = 0.75f + secondHalfDurationScale = 0.5f + stashHandleAlphaValue = 1f + fullLengthAnimatorSet.play( + bubbleBarTranslationYAnimator.animateToValue(getStashTranslation()) + ) + firstHalfAnimatorSet.playTogether( + bubbleBarAlpha.animateToValue(0f), + bubbleBarScale.animateToValue(STASHED_BAR_SCALE) + ) + } else { + firstHalfDurationScale = 0.5f + secondHalfDurationScale = 0.75f + stashHandleAlphaValue = 0f + fullLengthAnimatorSet.playTogether( + bubbleBarScale.animateToValue(1f), + bubbleBarTranslationYAnimator.animateToValue(bubbleBarTranslationY) + ) + secondHalfAnimatorSet.playTogether(bubbleBarAlpha.animateToValue(1f)) + } + stashHandleViewAlpha?.let { + secondHalfAnimatorSet.playTogether(it.animateToValue(stashHandleAlphaValue)) + } + bubbleStashedHandleViewController?.createRevealAnimToIsStashed(isStashed)?.let { + fullLengthAnimatorSet.play(it) + } + fullLengthAnimatorSet.setDuration(duration) + firstHalfAnimatorSet.setDuration((duration * firstHalfDurationScale).toLong()) + secondHalfAnimatorSet.setDuration((duration * secondHalfDurationScale).toLong()) + secondHalfAnimatorSet.startDelay = (duration * (1 - secondHalfDurationScale)).toLong() + animatorSet.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet, secondHalfAnimatorSet) + animatorSet.addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + animator = null + controllersAfterInitAction.runAfterInit { + if (isStashed) { + bubbleBarViewController.isExpanded = false + } + taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged() + } + } + } + ) + return animatorSet + } + + private fun onIsStashedChanged() { + controllersAfterInitAction.runAfterInit { + taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged() + bubbleStashedHandleViewController?.onIsStashedChanged() + } + } + + private fun animateBubbleBarYToHotseat() { + translateBubbleBarYUpdateTouchRegionOnCompletion(bubbleBarTranslationYForHotseat) + } + + private fun animateBubbleBarYToTaskbar() { + translateBubbleBarYUpdateTouchRegionOnCompletion(bubbleBarTranslationYForTaskbar) + } + + private fun translateBubbleBarYUpdateTouchRegionOnCompletion(toY: Float) { + bubbleBarViewController.bubbleBarTranslationY + .animateToValue(toY) + .updateTouchRegionOnAnimationEnd() + .setDuration(BAR_TRANSLATION_DURATION) + .start() + } + + @VisibleForTesting + fun updateStashedAndExpandedState(stash: Boolean, expand: Boolean) { + if (bubbleBarViewController.isHiddenForNoBubbles) { + // If there are no bubbles the bar and handle are invisible, nothing to do here. + return + } + val isStashed = stash && !isBubblesShowingOnHome && !isBubblesShowingOnOverview + if (this.isStashed != isStashed) { + this.isStashed = 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 + bubbleBarViewController.onStashStateChanging() + animator?.cancel() + animator = + createStashAnimator(isStashed, BAR_STASH_DURATION).apply { + updateTouchRegionOnAnimationEnd() + start() + } + } + if (bubbleBarViewController.isExpanded != expand) { + bubbleBarViewController.isExpanded = expand + } + } + + private fun Animator.updateTouchRegionOnAnimationEnd(): Animator { + this.addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + onIsStashedChanged() + } + } + ) + return this + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentTaskbarStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt similarity index 98% rename from quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentTaskbarStashControllerTest.kt rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt index c46c08d860..c0a5dfaf7b 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentTaskbarStashControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt @@ -40,10 +40,10 @@ import org.mockito.kotlin.clearInvocations import org.mockito.kotlin.verify import org.mockito.kotlin.whenever -/** Unit tests for [PersistentTaskbarStashController]. */ +/** Unit tests for [PersistentBubbleStashController]. */ @SmallTest @RunWith(AndroidJUnit4::class) -class PersistentTaskbarStashControllerTest { +class PersistentBubbleStashControllerTest { companion object { const val BUBBLE_BAR_HEIGHT = 100f @@ -52,15 +52,17 @@ class PersistentTaskbarStashControllerTest { } @get:Rule val animatorTestRule: AnimatorTestRule = AnimatorTestRule(this) + @get:Rule val rule: MockitoRule = MockitoJUnit.rule() private val context = ApplicationProvider.getApplicationContext() private lateinit var bubbleBarView: BubbleBarView @Mock lateinit var bubbleBarViewController: BubbleBarViewController + @Mock lateinit var taskbarInsetsController: TaskbarInsetsController - private lateinit var persistentTaskBarStashController: PersistentTaskbarStashController + private lateinit var persistentTaskBarStashController: PersistentBubbleStashController private lateinit var translationY: AnimatedFloat private lateinit var scale: AnimatedFloat private lateinit var alpha: MultiValueAlpha @@ -68,7 +70,7 @@ class PersistentTaskbarStashControllerTest { @Before fun setUp() { persistentTaskBarStashController = - PersistentTaskbarStashController(DefaultDimensionsProvider()) + PersistentBubbleStashController(DefaultDimensionsProvider()) setUpBubbleBarView() setUpBubbleBarController() persistentTaskBarStashController.init( diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/StashingTestUtils.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/StashingTestUtils.kt index 5dc9440d12..00ad3b7333 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/StashingTestUtils.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/StashingTestUtils.kt @@ -20,14 +20,19 @@ class ImmediateAction : BubbleStashController.ControllersAfterInitAction { override fun runAfterInit(action: () -> Unit) = action.invoke() } -class DefaultDimensionsProvider : BubbleStashController.TaskbarHotseatDimensionsProvider { - override fun getTaskbarBottomSpace(): Int = TASKBAR_BOTTOM_SPACE +class DefaultDimensionsProvider( + private val taskBarBottomSpace: Int = TASKBAR_BOTTOM_SPACE, + private val taskBarHeight: Int = TASKBAR_HEIGHT, + private val hotseatBottomSpace: Int = HOTSEAT_BOTTOM_SPACE, + private val hotseatHeight: Int = HOTSEAT_HEIGHT +) : BubbleStashController.TaskbarHotseatDimensionsProvider { + override fun getTaskbarBottomSpace(): Int = taskBarBottomSpace - override fun getTaskbarHeight(): Int = TASKBAR_HEIGHT + override fun getTaskbarHeight(): Int = taskBarHeight - override fun getHotseatBottomSpace(): Int = HOTSEAT_BOTTOM_SPACE + override fun getHotseatBottomSpace(): Int = hotseatBottomSpace - override fun getHotseatHeight(): Int = HOTSEAT_HEIGHT + override fun getHotseatHeight(): Int = hotseatHeight companion object { const val TASKBAR_BOTTOM_SPACE = 0 diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt new file mode 100644 index 0000000000..b5809c2b78 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar.bubbles.stashing + +import android.animation.AnimatorTestRule +import android.content.Context +import android.view.View +import android.widget.FrameLayout +import androidx.dynamicanimation.animation.DynamicAnimation +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import com.android.launcher3.anim.AnimatedFloat +import com.android.launcher3.taskbar.StashedHandleView +import com.android.launcher3.taskbar.TaskbarInsetsController +import com.android.launcher3.taskbar.bubbles.BubbleBarView +import com.android.launcher3.taskbar.bubbles.BubbleBarViewController +import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController +import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.STASHED_BAR_SCALE +import com.android.launcher3.util.MultiValueAlpha +import com.android.wm.shell.shared.animation.PhysicsAnimator +import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.mockito.kotlin.any +import org.mockito.kotlin.atLeastOnce +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +/** Unit tests for [TransientBubbleStashController]. */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class TransientBubbleStashControllerTest { + + companion object { + const val TASKBAR_BOTTOM_SPACE = 5 + const val BUBBLE_BAR_HEIGHT = 100f + const val HOTSEAT_TRANSLATION_Y = -45f + const val TASK_BAR_TRANSLATION_Y = -TASKBAR_BOTTOM_SPACE + const val HANDLE_VIEW_HEIGHT = 4 + const val BUBBLE_BAR_STASHED_TRANSLATION_Y = 48 + } + + @get:Rule val animatorTestRule: AnimatorTestRule = AnimatorTestRule(this) + + @get:Rule val rule: MockitoRule = MockitoJUnit.rule() + + @Mock lateinit var bubbleStashedHandleViewController: BubbleStashedHandleViewController + + @Mock lateinit var bubbleBarViewController: BubbleBarViewController + + @Mock lateinit var taskbarInsetsController: TaskbarInsetsController + + private val context = ApplicationProvider.getApplicationContext() + private lateinit var bubbleBarView: BubbleBarView + private lateinit var stashedHandleView: StashedHandleView + private lateinit var barTranslationY: AnimatedFloat + private lateinit var barScale: AnimatedFloat + private lateinit var barAlpha: MultiValueAlpha + private lateinit var stashedHandleAlpha: MultiValueAlpha + private lateinit var stashedHandleScale: AnimatedFloat + private lateinit var stashedHandleTranslationY: AnimatedFloat + private lateinit var stashPhysicsAnimator: PhysicsAnimator + + private lateinit var mTransientBubbleStashController: TransientBubbleStashController + + @Before + fun setUp() { + val taskbarHotseatDimensionsProvider = + DefaultDimensionsProvider(taskBarBottomSpace = TASKBAR_BOTTOM_SPACE) + mTransientBubbleStashController = + TransientBubbleStashController(taskbarHotseatDimensionsProvider, context.resources) + setUpBubbleBarView() + setUpBubbleBarController() + setUpStashedHandleView() + setUpBubbleStashedHandleViewController() + PhysicsAnimatorTestUtils.prepareForTest() + mTransientBubbleStashController.init( + taskbarInsetsController, + bubbleBarViewController, + bubbleStashedHandleViewController, + ImmediateAction() + ) + } + + @Test + fun setBubblesShowingOnHomeUpdatedToTrue_barPositionYUpdated_controllersNotified() { + // Given bubble bar is on home and has bubbles + whenever(bubbleBarViewController.hasBubbles()).thenReturn(true) + + // When switch out of the home screen + getInstrumentation().runOnMainSync { + mTransientBubbleStashController.isBubblesShowingOnHome = true + } + + // Then BubbleBarView is animating, BubbleBarViewController controller is notified + assertThat(barTranslationY.isAnimating).isTrue() + verify(bubbleBarViewController).onBubbleBarConfigurationChanged(/* animate= */ true) + + // Wait until animation ends + advanceTimeBy(BubbleStashController.BAR_TRANSLATION_DURATION) + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + // Then translation Y is correct and the insets controller is notified + assertThat(barTranslationY.isAnimating).isFalse() + verify(taskbarInsetsController).onTaskbarOrBubblebarWindowHeightOrInsetsChanged() + assertThat(bubbleBarView.translationY).isEqualTo(HOTSEAT_TRANSLATION_Y) + } + + @Test + fun setBubblesShowingOnOverviewUpdatedToTrue_barPositionYUpdated_controllersNotified() { + // Given bubble bar is on home and has bubbles + whenever(bubbleBarViewController.hasBubbles()).thenReturn(true) + + // When switch out of the home screen + getInstrumentation().runOnMainSync { + mTransientBubbleStashController.isBubblesShowingOnOverview = true + } + + // Then BubbleBarView is animating, BubbleBarViewController controller is notified + assertThat(barTranslationY.isAnimating).isTrue() + verify(bubbleBarViewController).onBubbleBarConfigurationChanged(/* animate= */ true) + + // Wait until animation ends + advanceTimeBy(BubbleStashController.BAR_TRANSLATION_DURATION) + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + // Then translation Y is correct and the insets controller is notified + assertThat(barTranslationY.isAnimating).isFalse() + verify(taskbarInsetsController).onTaskbarOrBubblebarWindowHeightOrInsetsChanged() + assertThat(bubbleBarView.translationY).isEqualTo(TASK_BAR_TRANSLATION_Y) + } + + @Test + fun updateStashedAndExpandedState_stashAndCollapse_bubbleBarHidden_stashedHandleShown() { + // Given bubble bar has bubbles and not stashed + mTransientBubbleStashController.isStashed = false + whenever(bubbleBarViewController.isHiddenForNoBubbles).thenReturn(false) + + // When stash + getInstrumentation().runOnMainSync { + mTransientBubbleStashController.updateStashedAndExpandedState( + stash = true, + expand = false + ) + } + + // Wait until animations ends + advanceTimeBy(BubbleStashController.BAR_STASH_DURATION) + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + // Then check BubbleBarController is notified + verify(bubbleBarViewController).onStashStateChanging() + // Bubble bar is stashed + assertThat(mTransientBubbleStashController.isStashed).isTrue() + assertThat(bubbleBarView.translationY).isEqualTo(BUBBLE_BAR_STASHED_TRANSLATION_Y) + assertThat(bubbleBarView.alpha).isEqualTo(0f) + assertThat(bubbleBarView.scaleX).isEqualTo(STASHED_BAR_SCALE) + assertThat(bubbleBarView.scaleY).isEqualTo(STASHED_BAR_SCALE) + // Handle view is visible + assertThat(stashedHandleView.translationY).isEqualTo(0) + assertThat(stashedHandleView.alpha).isEqualTo(1) + } + + @Test + fun isSysuiLockedSwitchedToFalseForOverview_unlockAnimationIsShown() { + // Given screen is locked and bubble bar has bubbles + getInstrumentation().runOnMainSync { + mTransientBubbleStashController.isSysuiLocked = true + mTransientBubbleStashController.isBubblesShowingOnOverview = true + whenever(bubbleBarViewController.hasBubbles()).thenReturn(true) + } + advanceTimeBy(BubbleStashController.BAR_TRANSLATION_DURATION) + + // When switch to the overview screen + getInstrumentation().runOnMainSync { mTransientBubbleStashController.isSysuiLocked = false } + + // Then + assertThat(barTranslationY.isAnimating).isTrue() + assertThat(barScale.isAnimating).isTrue() + // Wait until animation ends + advanceTimeBy(BubbleStashController.BAR_STASH_DURATION) + + // Then bubble bar is fully visible at the correct location + assertThat(bubbleBarView.scaleX).isEqualTo(1f) + assertThat(bubbleBarView.scaleY).isEqualTo(1f) + assertThat(bubbleBarView.translationY) + .isEqualTo(PersistentBubbleStashControllerTest.TASK_BAR_TRANSLATION_Y) + assertThat(bubbleBarView.alpha).isEqualTo(1f) + // Insets controller is notified + verify(taskbarInsetsController, atLeastOnce()) + .onTaskbarOrBubblebarWindowHeightOrInsetsChanged() + } + + @Test + fun showBubbleBarImmediateToY() { + // Given bubble bar is fully transparent and scaled to 0 at 0 y position + val targetY = 341f + bubbleBarView.alpha = 0f + bubbleBarView.scaleX = 0f + bubbleBarView.scaleY = 0f + bubbleBarView.translationY = 0f + stashedHandleView.translationY = targetY + + // When + mTransientBubbleStashController.showBubbleBarImmediate(targetY) + + // Then all property values are updated + assertThat(bubbleBarView.translationY).isEqualTo(targetY) + assertThat(bubbleBarView.alpha).isEqualTo(1f) + assertThat(bubbleBarView.scaleX).isEqualTo(1f) + assertThat(bubbleBarView.scaleY).isEqualTo(1f) + // Handle is transparent + assertThat(stashedHandleView.alpha).isEqualTo(0) + // Insets controller is notified + verify(taskbarInsetsController).onTaskbarOrBubblebarWindowHeightOrInsetsChanged() + } + + @Test + fun stashBubbleBarImmediate() { + // When + mTransientBubbleStashController.stashBubbleBarImmediate() + + // Then all property values are updated + assertThat(bubbleBarView.translationY).isEqualTo(BUBBLE_BAR_STASHED_TRANSLATION_Y) + assertThat(bubbleBarView.alpha).isEqualTo(0) + assertThat(bubbleBarView.scaleX).isEqualTo(STASHED_BAR_SCALE) + assertThat(bubbleBarView.scaleY).isEqualTo(STASHED_BAR_SCALE) + // Handle is visible at correct Y position + assertThat(stashedHandleView.alpha).isEqualTo(1) + assertThat(stashedHandleView.translationY).isEqualTo(0) + // Insets controller is notified + verify(taskbarInsetsController).onTaskbarOrBubblebarWindowHeightOrInsetsChanged() + } + + @Test + fun getTouchableHeight_stashed_stashHeightReturned() { + // When + mTransientBubbleStashController.isStashed = true + val height = mTransientBubbleStashController.getTouchableHeight() + + // Then + assertThat(height).isEqualTo(HANDLE_VIEW_HEIGHT) + } + + @Test + fun getTouchableHeight_unstashed_barHeightReturned() { + // When BubbleBar is not stashed + mTransientBubbleStashController.isStashed = false + val height = mTransientBubbleStashController.getTouchableHeight() + + // Then bubble bar height is returned + assertThat(height).isEqualTo(BUBBLE_BAR_HEIGHT.toInt()) + } + + private fun advanceTimeBy(advanceMs: Long) { + // Advance animator for on-device tests + getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(advanceMs) } + } + + private fun setUpBubbleBarView() { + getInstrumentation().runOnMainSync { + bubbleBarView = BubbleBarView(context) + bubbleBarView.layoutParams = FrameLayout.LayoutParams(0, 0) + } + } + + private fun setUpStashedHandleView() { + getInstrumentation().runOnMainSync { + stashedHandleView = StashedHandleView(context) + stashedHandleView.layoutParams = FrameLayout.LayoutParams(0, 0) + } + } + + private fun setUpBubbleBarController() { + barTranslationY = + AnimatedFloat(Runnable { bubbleBarView.translationY = barTranslationY.value }) + barScale = + AnimatedFloat( + Runnable { + val scale: Float = barScale.value + bubbleBarView.scaleX = scale + bubbleBarView.scaleY = scale + } + ) + barAlpha = MultiValueAlpha(bubbleBarView, 1 /* num alpha channels */) + + whenever(bubbleBarViewController.hasBubbles()).thenReturn(true) + whenever(bubbleBarViewController.bubbleBarTranslationY).thenReturn(barTranslationY) + whenever(bubbleBarViewController.bubbleBarScale).thenReturn(barScale) + whenever(bubbleBarViewController.bubbleBarAlpha).thenReturn(barAlpha) + whenever(bubbleBarViewController.bubbleBarCollapsedHeight).thenReturn(BUBBLE_BAR_HEIGHT) + } + + private fun setUpBubbleStashedHandleViewController() { + stashedHandleTranslationY = + AnimatedFloat(Runnable { stashedHandleView.translationY = barTranslationY.value }) + stashedHandleScale = + AnimatedFloat( + Runnable { + val scale: Float = barScale.value + bubbleBarView.scaleX = scale + bubbleBarView.scaleY = scale + } + ) + stashedHandleAlpha = MultiValueAlpha(stashedHandleView, 1 /* num alpha channels */) + stashPhysicsAnimator = PhysicsAnimator.getInstance(stashedHandleView) + whenever(bubbleStashedHandleViewController.stashedHandleAlpha) + .thenReturn(stashedHandleAlpha) + whenever(bubbleStashedHandleViewController.physicsAnimator).thenReturn(stashPhysicsAnimator) + whenever(bubbleStashedHandleViewController.stashedHeight).thenReturn(HANDLE_VIEW_HEIGHT) + whenever(bubbleStashedHandleViewController.setTranslationYForSwipe(any())).thenAnswer { + invocation -> + (invocation.arguments[0] as Float).also { stashedHandleView.translationY = it } + } + } +}