Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6570ac75 authored by Ats Jenk's avatar Ats Jenk
Browse files

Update bubble bar swipe logic

After changes following swipe interactions are available for the bubble
bar:
1. stashed handle
- swipe up to show bubble bar, after swipe reaches unstash threshold, we
  show the bubble bar
- swipe down to stash bubble bar in the same gesture (without lifting
  finger), do this when swipe moves back below unstash threshold
- expand bubble bar on finger lifted when currently above unstash
  threshold

2. collapsed bubble bar (used in home or overview)
- swipe up and release over unstash threshold to expand bubble bar
- bubble bar can't be swiped down to stash it

3. expanded bubble bar
- no swipe interactions available

Bug: 371229061
Flag: com.android.wm.shell.enable_bubble_bar
Test: BubbleBarSwipeControllerTest
Test: manually test:
  - stashed bubble bar:
      - swipe up and back down => bubble bar shown and stashed
      - swipe up slightly and release => bubble bar not shown
      - swipe up and release => bubble bar shown, expands on release
  - collapsed bubble bar:
      - swipe down => no action
      - swipe up slightly => no action
      - swipe up and release => bar expands on release
Change-Id: I42aa59dc288446603e06a3d02419be38cf17fa1f
parent e79a786b
Loading
Loading
Loading
Loading
+42 −72
Original line number Diff line number Diff line
@@ -21,15 +21,14 @@ import android.content.Context
import androidx.annotation.VisibleForTesting
import androidx.core.animation.doOnEnd
import androidx.dynamicanimation.animation.SpringForce
import com.android.launcher3.R
import com.android.launcher3.anim.AnimatedFloat
import com.android.launcher3.anim.SpringAnimationBuilder
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.taskbar.TaskbarThresholdUtils
import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.StartState.COLLAPSED
import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.StartState.EXPANDED
import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.StartState.STASHED
import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.StartState.UNKNOWN
import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.BarState.COLLAPSED
import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.BarState.EXPANDED
import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.BarState.STASHED
import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.BarState.UNKNOWN
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
import com.android.launcher3.touch.OverScroll

@@ -46,11 +45,9 @@ class BubbleBarSwipeController {
    private val animatedSwipeTranslation = AnimatedFloat(this::onSwipeUpdate)

    private val unstashThreshold: Int
    private val expandThreshold: Int
    private val maxOverscroll: Int
    private val stashThreshold: Int

    private var swipeState: SwipeState = SwipeState()
    private var swipeState: SwipeState = SwipeState(startState = UNKNOWN)

    constructor(tac: TaskbarActivityContext) : this(tac, DefaultDimensionProvider(tac))

@@ -58,9 +55,7 @@ class BubbleBarSwipeController {
    constructor(context: Context, dimensionProvider: DimensionProvider) {
        this.context = context
        unstashThreshold = dimensionProvider.unstashThreshold
        expandThreshold = dimensionProvider.expandThreshold
        maxOverscroll = dimensionProvider.maxOverscroll
        stashThreshold = dimensionProvider.stashThreshold
    }

    fun init(bubbleControllers: BubbleControllers) {
@@ -80,7 +75,7 @@ class BubbleBarSwipeController {
                bubbleStashController.isBubbleBarVisible() -> COLLAPSED
                else -> UNKNOWN
            }
        swipeState = SwipeState(startState = startState)
        swipeState = SwipeState(startState = startState, currentState = startState)
    }

    /** Update swipe distance to [dy] */
@@ -90,47 +85,26 @@ class BubbleBarSwipeController {
        }
        animatedSwipeTranslation.updateValue(dy)

        val prevState = swipeState
        // We can pass unstash threshold once per gesture, keep it true if it happened once
        val passedUnstashThreshold = isUnstash(dy) || prevState.passedUnstashThreshold
        // Expand happens at the end of the gesture, always keep the current value
        val passedExpandThreshold = isExpand(dy)
        // Stash happens at the end of the gesture, always keep the current value
        val passedStashThreshold = isStash(dy)

        if (
            passedUnstashThreshold != prevState.passedUnstashThreshold ||
                passedExpandThreshold != prevState.passedExpandThreshold ||
                passedStashThreshold != prevState.passedStashThreshold
        ) {
            swipeState =
                swipeState.copy(
                    passedUnstashThreshold = passedUnstashThreshold,
                    passedExpandThreshold = passedExpandThreshold,
                    passedStashThreshold = passedStashThreshold,
                )
        }

        if (
            swipeState.startState == STASHED &&
                swipeState.passedUnstashThreshold &&
                !prevState.passedUnstashThreshold
        ) {
        swipeState.passedUnstash = isUnstash(dy)
        // Tracking swipe gesture if we pass unstash threshold at least once during gesture
        swipeState.isSwipe = swipeState.isSwipe || swipeState.passedUnstash
        when {
            canUnstash() && swipeState.passedUnstash -> {
                swipeState.currentState = COLLAPSED
                bubbleStashController.showBubbleBar(expandBubbles = false)
            }
            canStash() && !swipeState.passedUnstash -> {
                swipeState.currentState = STASHED
                bubbleStashController.stashBubbleBar()
            }
        }
    }

    /** Finish tracking swipe gesture. Animate views back to resting state */
    fun finish() {
        when {
            swipeState.passedExpandThreshold &&
                swipeState.startState in setOf(STASHED, COLLAPSED) -> {
        if (swipeState.passedUnstash && swipeState.startState in setOf(STASHED, COLLAPSED)) {
            bubbleStashController.showBubbleBar(expandBubbles = true)
        }
            swipeState.passedStashThreshold && swipeState.startState == COLLAPSED -> {
                bubbleStashController.stashBubbleBar()
            }
        }
        if (animatedSwipeTranslation.value == 0f) {
            reset()
        } else {
@@ -140,15 +114,21 @@ class BubbleBarSwipeController {

    /** Returns `true` if we are tracking a swipe gesture */
    fun isSwipeGesture(): Boolean {
        return swipeState.passedUnstashThreshold ||
            swipeState.passedExpandThreshold ||
            swipeState.passedStashThreshold
        return swipeState.isSwipe
    }

    private fun canHandleSwipe(dy: Float): Boolean {
        return when (swipeState.startState) {
            STASHED -> dy < 0 // stashed bar only handles swipe up
            COLLAPSED -> true // collapsed bar can be swiped in either direction
            STASHED -> {
                if (swipeState.currentState == COLLAPSED) {
                    // if we have unstashed the bar, allow swipe in both directions
                    true
                } else {
                    // otherwise, only allow swipe up on stash handle
                    dy < 0
                }
            }
            COLLAPSED -> dy < 0 // collapsed bar can only be swiped up
            UNKNOWN,
            EXPANDED -> false // expanded bar can't be swiped
        }
@@ -158,12 +138,13 @@ class BubbleBarSwipeController {
        return dy < -unstashThreshold
    }

    private fun isExpand(dy: Float): Boolean {
        return dy < -expandThreshold
    private fun canStash(): Boolean {
        // Only allow stashing if we started from stashed state
        return swipeState.startState == STASHED && swipeState.currentState == COLLAPSED
    }

    private fun isStash(dy: Float): Boolean {
        return dy > stashThreshold
    private fun canUnstash(): Boolean {
        return swipeState.currentState == STASHED
    }

    private fun reset() {
@@ -175,7 +156,7 @@ class BubbleBarSwipeController {
            }
        }
        springAnimation = null
        swipeState = SwipeState()
        swipeState = SwipeState(startState = UNKNOWN)
    }

    private fun onSwipeUpdate(value: Float) {
@@ -197,13 +178,13 @@ class BubbleBarSwipeController {
    }

    internal data class SwipeState(
        val startState: StartState = UNKNOWN,
        val passedUnstashThreshold: Boolean = false,
        val passedExpandThreshold: Boolean = false,
        val passedStashThreshold: Boolean = false,
        val startState: BarState,
        var currentState: BarState = UNKNOWN,
        var passedUnstash: Boolean = false,
        var isSwipe: Boolean = false,
    )

    internal enum class StartState {
    internal enum class BarState {
        UNKNOWN,
        STASHED,
        COLLAPSED,
@@ -214,17 +195,13 @@ class BubbleBarSwipeController {
    @VisibleForTesting
    interface DimensionProvider {
        val unstashThreshold: Int
        val expandThreshold: Int
        val maxOverscroll: Int
        val stashThreshold: Int
    }

    private class DefaultDimensionProvider(taskbarActivityContext: TaskbarActivityContext) :
        DimensionProvider {
        override val unstashThreshold: Int
        override val expandThreshold: Int
        override val maxOverscroll: Int
        override val stashThreshold: Int

        init {
            val resources = taskbarActivityContext.resources
@@ -233,14 +210,7 @@ class BubbleBarSwipeController {
                    resources,
                    taskbarActivityContext.deviceProfile,
                )
            // TODO(325673340): review threshold with ux
            expandThreshold =
                TaskbarThresholdUtils.getAppWindowThreshold(
                    resources,
                    taskbarActivityContext.deviceProfile,
                )
            maxOverscroll = taskbarActivityContext.deviceProfile.heightPx - unstashThreshold
            stashThreshold = resources.getDimensionPixelSize(R.dimen.taskbar_to_nav_threshold)
        }
    }
}
+58 −91
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

@@ -45,15 +46,11 @@ class BubbleBarSwipeControllerTest {

    companion object {
        const val UNSTASH_THRESHOLD = 100
        const val EXPAND_THRESHOLD = 200
        const val MAX_OVERSCROLL = 300
        const val STASH_THRESHOLD = 50

        const val UP_BELOW_UNSTASH = -UNSTASH_THRESHOLD + 10f
        const val UP_ABOVE_UNSTASH = -UNSTASH_THRESHOLD - 10f
        const val UP_ABOVE_EXPAND = -EXPAND_THRESHOLD - 10f
        const val DOWN_UNDER_STASH = STASH_THRESHOLD - 10f
        const val DOWN_OVER_STASH = STASH_THRESHOLD + 10f
        const val DOWN = UNSTASH_THRESHOLD + 10f
    }

    private val context = ApplicationProvider.getApplicationContext<Context>()
@@ -80,14 +77,8 @@ class BubbleBarSwipeControllerTest {
                override val unstashThreshold: Int
                    get() = UNSTASH_THRESHOLD

                override val expandThreshold: Int
                    get() = EXPAND_THRESHOLD

                override val maxOverscroll: Int
                    get() = MAX_OVERSCROLL

                override val stashThreshold: Int
                    get() = STASH_THRESHOLD
            }
        bubbleBarSwipeController = BubbleBarSwipeController(context, dimensionProvider)

@@ -134,36 +125,12 @@ class BubbleBarSwipeControllerTest {
        testViewsHaveDampedTranslationOnSwipe(UP_ABOVE_UNSTASH)
    }

    @Test
    fun swipeUp_stashedBar_aboveExpandThreshold_viewsHaveDampedTranslation() {
        setUpStashedBar()
        testViewsHaveDampedTranslationOnSwipe(UP_ABOVE_EXPAND)
    }

    @Test
    fun swipeUp_collapsedBar_aboveUnstashThreshold_viewsHaveDampedTranslation() {
        setUpCollapsedBar()
        testViewsHaveDampedTranslationOnSwipe(UP_ABOVE_UNSTASH)
    }

    @Test
    fun swipeUp_collapsedBar_aboveExpandThreshold_viewsHaveDampedTranslation() {
        setUpCollapsedBar()
        testViewsHaveDampedTranslationOnSwipe(UP_ABOVE_EXPAND)
    }

    @Test
    fun swipeDown_collapsedBar_belowStashThreshold_viewsHaveDampedTranslation() {
        setUpCollapsedBar()
        testViewsHaveDampedTranslationOnSwipe(DOWN_UNDER_STASH)
    }

    @Test
    fun swipeDown_collapsedBar_overStashThreshold_viewsHaveDampedTranslation() {
        setUpCollapsedBar()
        testViewsHaveDampedTranslationOnSwipe(DOWN_OVER_STASH)
    }

    // endregion

    // region Test that translation on views is reset on finish
@@ -202,30 +169,12 @@ class BubbleBarSwipeControllerTest {
        testViewsTranslationResetOnFinish(UP_ABOVE_UNSTASH)
    }

    @Test
    fun swipeUp_stashedBar_aboveExpandThreshold_animateTranslationToZeroOnFinish() {
        setUpStashedBar()
        testViewsTranslationResetOnFinish(UP_ABOVE_EXPAND)
    }

    @Test
    fun swipeUp_collapsedBar_aboveUnstashThreshold_animateTranslationToZeroOnFinish() {
        setUpCollapsedBar()
        testViewsTranslationResetOnFinish(UP_ABOVE_UNSTASH)
    }

    @Test
    fun swipeUp_collapsedBar_aboveExpandThreshold_animateTranslationToZeroOnFinish() {
        setUpCollapsedBar()
        testViewsTranslationResetOnFinish(UP_ABOVE_EXPAND)
    }

    @Test
    fun swipeDown_collapsedBar_aboveStashThreshold_animateTranslationToZeroOnFinish() {
        setUpCollapsedBar()
        testViewsTranslationResetOnFinish(DOWN_OVER_STASH)
    }

    // endregion

    // region Test swipe interactions on stashed bar
@@ -251,7 +200,7 @@ class BubbleBarSwipeControllerTest {
    }

    @Test
    fun swipeUp_stashedBar_aboveUnstashThreshold_unstashBubbleBar() {
    fun swipeUp_stashedBar_overUnstashThreshold_unstashBubbleBar() {
        setUpStashedBar()
        getInstrumentation().runOnMainSync {
            bubbleBarSwipeController.start()
@@ -271,50 +220,45 @@ class BubbleBarSwipeControllerTest {
    }

    @Test
    fun swipeUp_stashedBar_overUnstashThresholdMultipleTimes_unstashBubbleBarOnce() {
    fun swipeUp_stashedBar_overUnstashThresholdMultipleTimes_unstashesMultipleTimes() {
        setUpStashedBar()
        getInstrumentation().runOnMainSync {
            bubbleBarSwipeController.start()
            bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
            bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH)
            bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
        }
        verify(bubbleStashController).showBubbleBar(expandBubbles = false)
        verify(bubbleStashController).stashBubbleBar()

        getInstrumentation().runOnMainSync { bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH) }
        verify(bubbleStashController, times(2)).showBubbleBar(expandBubbles = false)
    }

    @Test
    fun swipeUp_stashedBar_overExpandThreshold_doesNotExpandBeforeFinish() {
    fun swipeUp_stashedBar_releaseOverUnstashThreshold_expandsBar() {
        setUpStashedBar()
        getInstrumentation().runOnMainSync {
            bubbleBarSwipeController.start()
            bubbleBarSwipeController.swipeTo(UP_ABOVE_EXPAND)
            bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
        }
        verify(bubbleStashController).showBubbleBar(expandBubbles = false)
        verify(bubbleStashController, never()).showBubbleBar(expandBubbles = true)
        getInstrumentation().runOnMainSync { bubbleBarSwipeController.finish() }
        verify(bubbleStashController).showBubbleBar(expandBubbles = true)
    }

    @Test
    fun swipeUp_stashedBar_overExpandThreshold_isSwipeGestureTrue() {
    fun swipeUp_stashedBar_overUnstashReleaseBelowUnstash_doesNotExpandBar() {
        setUpStashedBar()
        getInstrumentation().runOnMainSync {
            bubbleBarSwipeController.start()
            bubbleBarSwipeController.swipeTo(UP_ABOVE_EXPAND)
        }
        assertThat(bubbleBarSwipeController.isSwipeGesture()).isTrue()
    }

    @Test
    fun swipeUp_stashedBar_overExpandThresholdAndBackDown_doesNotExpandAfterFinish() {
        setUpStashedBar()
        getInstrumentation().runOnMainSync {
            bubbleBarSwipeController.start()
            bubbleBarSwipeController.swipeTo(UP_ABOVE_EXPAND)
            bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
        }
        verify(bubbleStashController).showBubbleBar(expandBubbles = false)
        getInstrumentation().runOnMainSync { bubbleBarSwipeController.finish() }
        verify(bubbleStashController).showBubbleBar(expandBubbles = false)
        getInstrumentation().runOnMainSync {
            bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH)
            bubbleBarSwipeController.finish()
        }
        verify(bubbleStashController, never()).showBubbleBar(expandBubbles = true)
    }

    @Test
@@ -322,7 +266,7 @@ class BubbleBarSwipeControllerTest {
        setUpStashedBar()
        getInstrumentation().runOnMainSync {
            bubbleBarSwipeController.start()
            bubbleBarSwipeController.swipeTo(DOWN_OVER_STASH)
            bubbleBarSwipeController.swipeTo(DOWN)
        }
        verify(bubbleStashedHandleViewController, never()).setTranslationYForSwipe(any())
        verify(bubbleBarViewController, never()).setTranslationYForSwipe(any())
@@ -338,8 +282,8 @@ class BubbleBarSwipeControllerTest {
        setUpExpandedBar()
        getInstrumentation().runOnMainSync {
            bubbleBarSwipeController.start()
            bubbleBarSwipeController.swipeTo(UP_ABOVE_EXPAND)
            bubbleBarSwipeController.swipeTo(DOWN_OVER_STASH)
            bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
            bubbleBarSwipeController.swipeTo(DOWN)
            bubbleBarSwipeController.finish()
        }
        verify(bubbleStashedHandleViewController, never()).setTranslationYForSwipe(any())
@@ -352,46 +296,69 @@ class BubbleBarSwipeControllerTest {
    // region Test swipe interactions on collapsed bar

    @Test
    fun swipeDown_collapsedBar_underStashThreshold_doesNotHideBar() {
    fun swipeUp_collapsedBar_doesNotShowBarDuringDrag() {
        setUpCollapsedBar()
        getInstrumentation().runOnMainSync {
            bubbleBarSwipeController.start()
            bubbleBarSwipeController.swipeTo(DOWN_UNDER_STASH)
            bubbleBarSwipeController.finish()
            bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH)
            bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
        }
        verify(bubbleStashController, never()).stashBubbleBar()
        verify(bubbleStashController, never()).showBubbleBar(any())
    }

    @Test
    fun swipeDown_collapsedBar_overStashThreshold_doesNotHideBarBeforeFinish() {
    fun swipeUp_collapsedBar_belowUnstashThreshold_isSwipeGestureFalse() {
        setUpCollapsedBar()
        getInstrumentation().runOnMainSync {
            bubbleBarSwipeController.start()
            bubbleBarSwipeController.swipeTo(DOWN_OVER_STASH)
            bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH)
        }
        verify(bubbleStashController, never()).stashBubbleBar()
        getInstrumentation().runOnMainSync { bubbleBarSwipeController.finish() }
        verify(bubbleStashController).stashBubbleBar()
        assertThat(bubbleBarSwipeController.isSwipeGesture()).isFalse()
    }

    @Test
    fun swipeDown_collapsedBar_underStashThreshold_isSwipeGestureFalse() {
    fun swipeUp_collapsedBar_overUnstashThreshold_isSwipeGestureTrue() {
        setUpCollapsedBar()
        getInstrumentation().runOnMainSync {
            bubbleBarSwipeController.start()
            bubbleBarSwipeController.swipeTo(DOWN_UNDER_STASH)
            bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
        }
        assertThat(bubbleBarSwipeController.isSwipeGesture()).isFalse()
        assertThat(bubbleBarSwipeController.isSwipeGesture()).isTrue()
    }

    @Test
    fun swipeDown_collapsedBar_overStashThreshold_isSwipeGestureTrue() {
    fun swipeUp_collapsedBar_finishOverUnstashThreshold_expandsBar() {
        setUpCollapsedBar()
        getInstrumentation().runOnMainSync {
            bubbleBarSwipeController.start()
            bubbleBarSwipeController.swipeTo(DOWN_OVER_STASH)
            bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
            bubbleBarSwipeController.finish()
        }
        assertThat(bubbleBarSwipeController.isSwipeGesture()).isTrue()
        verify(bubbleStashController).showBubbleBar(expandBubbles = true)
    }

    @Test
    fun swipeUp_collapsedBar_finishBelowUnstashThreshold_doesNotExpandBar() {
        setUpCollapsedBar()
        getInstrumentation().runOnMainSync {
            bubbleBarSwipeController.start()
            bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH)
            bubbleBarSwipeController.finish()
        }
        verify(bubbleStashController, never()).showBubbleBar(any())
    }

    @Test
    fun swipeDown_collapsedBar_swipeIgnored() {
        setUpCollapsedBar()
        getInstrumentation().runOnMainSync {
            bubbleBarSwipeController.start()
            bubbleBarSwipeController.swipeTo(DOWN)
        }
        verify(bubbleStashedHandleViewController, never()).setTranslationYForSwipe(any())
        verify(bubbleBarViewController, never()).setTranslationYForSwipe(any())
        verify(bubbleStashController, never()).showBubbleBar(any())
        verify(bubbleStashController, never()).stashBubbleBar()
    }

    // endregion