diff options
3 files changed, 98 insertions, 77 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt index 96e97565d585..91bb7898494f 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt @@ -60,15 +60,21 @@ private const val MIN_DURATION_CANCELLED_ANIMATION = 200L private const val MIN_DURATION_COMMITTED_ANIMATION = 80L private const val MIN_DURATION_COMMITTED_AFTER_FLING_ANIMATION = 120L private const val MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION = 50L -private const val MIN_DURATION_INACTIVE_BEFORE_ACTIVE_ANIMATION = 80L +private const val MIN_DURATION_INACTIVE_BEFORE_ACTIVE_ANIMATION = 160F +private const val MIN_DURATION_ENTRY_BEFORE_ACTIVE_ANIMATION = 10F +internal const val MAX_DURATION_ENTRY_BEFORE_ACTIVE_ANIMATION = 100F private const val MIN_DURATION_FLING_ANIMATION = 160L private const val MIN_DURATION_ENTRY_TO_ACTIVE_CONSIDERED_AS_FLING = 100L private const val MIN_DURATION_INACTIVE_TO_ACTIVE_CONSIDERED_AS_FLING = 400L private const val POP_ON_FLING_DELAY = 60L -private const val POP_ON_FLING_SCALE = 2f -private const val POP_ON_COMMITTED_SCALE = 3f +private const val POP_ON_FLING_VELOCITY = 2f +private const val POP_ON_COMMITTED_VELOCITY = 3f +private const val POP_ON_ACTIVE_MAX_VELOCITY = 2.5f +private const val POP_ON_ACTIVE_MIN_VELOCITY = 4.5f +private const val POP_ON_INACTIVE_MAX_VELOCITY = -1.05f +private const val POP_ON_INACTIVE_MIN_VELOCITY = -1.5f internal val VIBRATE_ACTIVATED_EFFECT = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK) @@ -156,13 +162,22 @@ class BackPanelController internal constructor( private var gestureEntryTime = 0L private var gestureInactiveTime = 0L - private var gesturePastActiveThresholdWhileInactiveTime = 0L private val elapsedTimeSinceInactive get() = SystemClock.uptimeMillis() - gestureInactiveTime private val elapsedTimeSinceEntry get() = SystemClock.uptimeMillis() - gestureEntryTime + + private var pastThresholdWhileEntryOrInactiveTime = 0L + private var entryToActiveDelay = 0F + private val entryToActiveDelayCalculation = { + convertVelocityToAnimationFactor( + valueOnFastVelocity = MIN_DURATION_ENTRY_BEFORE_ACTIVE_ANIMATION, + valueOnSlowVelocity = MAX_DURATION_ENTRY_BEFORE_ACTIVE_ANIMATION, + ) + } + // Whether the current gesture has moved a sufficiently large amount, // so that we can unambiguously start showing the ENTRY animation private var hasPassedDragSlop = false @@ -306,7 +321,9 @@ class BackPanelController internal constructor( MotionEvent.ACTION_UP -> { when (currentState) { GestureState.ENTRY -> { - if (isFlungAwayFromEdge(endX = event.x)) { + if (isFlungAwayFromEdge(endX = event.x) || + previousXTranslation > params.staticTriggerThreshold + ) { updateArrowState(GestureState.ACTIVE) updateArrowState(GestureState.FLUNG) } else { @@ -394,12 +411,29 @@ class BackPanelController internal constructor( } private fun updateArrowStateOnMove(yTranslation: Float, xTranslation: Float) { - val isWithinYActivationThreshold = xTranslation * 2 >= yTranslation - + val isPastStaticThreshold = xTranslation > params.staticTriggerThreshold when (currentState) { GestureState.ENTRY -> { - if (xTranslation > params.staticTriggerThreshold) { + if (isPastThresholdToActive( + isPastThreshold = isPastStaticThreshold, + dynamicDelay = entryToActiveDelayCalculation + ) + ) { + updateArrowState(GestureState.ACTIVE) + } + } + GestureState.INACTIVE -> { + val isPastDynamicReactivationThreshold = + totalTouchDeltaInactive >= params.reactivationTriggerThreshold + + if (isPastThresholdToActive( + isPastThreshold = isPastStaticThreshold && + isPastDynamicReactivationThreshold && + isWithinYActivationThreshold, + delay = MIN_DURATION_INACTIVE_BEFORE_ACTIVE_ANIMATION + ) + ) { updateArrowState(GestureState.ACTIVE) } } @@ -408,43 +442,12 @@ class BackPanelController internal constructor( totalTouchDeltaActive <= params.deactivationTriggerThreshold val isMinDurationElapsed = elapsedTimeSinceEntry > MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION - - if (isMinDurationElapsed && (!isWithinYActivationThreshold || - isPastDynamicDeactivationThreshold) - ) { + val isPastAllThresholds = + !isWithinYActivationThreshold || isPastDynamicDeactivationThreshold + if (isPastAllThresholds && isMinDurationElapsed) { updateArrowState(GestureState.INACTIVE) } } - GestureState.INACTIVE -> { - val isPastStaticThreshold = - xTranslation > params.staticTriggerThreshold - val isPastDynamicReactivationThreshold = - totalTouchDeltaInactive >= params.reactivationTriggerThreshold - val isPastAllThresholds = isPastStaticThreshold && - isPastDynamicReactivationThreshold && - isWithinYActivationThreshold - val isPastAllThresholdsForFirstTime = isPastAllThresholds && - gesturePastActiveThresholdWhileInactiveTime == 0L - - gesturePastActiveThresholdWhileInactiveTime = when { - isPastAllThresholdsForFirstTime -> SystemClock.uptimeMillis() - isPastAllThresholds -> gesturePastActiveThresholdWhileInactiveTime - else -> 0L - } - - val elapsedTimePastAllThresholds = - SystemClock.uptimeMillis() - gesturePastActiveThresholdWhileInactiveTime - - val isPastMinimumInactiveToActiveDuration = - elapsedTimePastAllThresholds > MIN_DURATION_INACTIVE_BEFORE_ACTIVE_ANIMATION - - if (isPastAllThresholds && isPastMinimumInactiveToActiveDuration) { - // The minimum duration adds the 'edge stickiness' - // factor before pulling it off edge - updateArrowState(GestureState.ACTIVE) - } - } - else -> {} } } @@ -672,6 +675,28 @@ class BackPanelController internal constructor( return flingDistance > minFlingDistance && isPastFlingVelocityThreshold } + private fun isPastThresholdToActive( + isPastThreshold: Boolean, + delay: Float? = null, + dynamicDelay: () -> Float = { delay ?: 0F } + ): Boolean { + val resetValue = 0L + val isPastThresholdForFirstTime = pastThresholdWhileEntryOrInactiveTime == resetValue + + if (!isPastThreshold) { + pastThresholdWhileEntryOrInactiveTime = resetValue + return false + } + + if (isPastThresholdForFirstTime) { + pastThresholdWhileEntryOrInactiveTime = SystemClock.uptimeMillis() + entryToActiveDelay = dynamicDelay() + } + val timePastThreshold = SystemClock.uptimeMillis() - pastThresholdWhileEntryOrInactiveTime + + return timePastThreshold > entryToActiveDelay + } + private fun playWithBackgroundWidthAnimation( onEnd: DelayedOnAnimationEndListener, delay: Long = 0L @@ -886,33 +911,16 @@ class BackPanelController internal constructor( } GestureState.ACTIVE -> { previousXTranslationOnActiveOffset = previousXTranslation - updateRestingArrowDimens() - vibratorHelper.cancel() mainHandler.postDelayed(10L) { vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT) } - - val minimumPop = 2f - val maximumPop = 4.5f - - when (previousState) { - GestureState.ENTRY -> { - val startingVelocity = convertVelocityToSpringStartingVelocity( - valueOnFastVelocity = minimumPop, - valueOnSlowVelocity = maximumPop, - fastVelocityBound = 1f, - slowVelocityBound = 0.5f, - ) - mView.popOffEdge(startingVelocity) - } - GestureState.INACTIVE -> { - mView.popOffEdge(maximumPop) - } - - else -> {} - } + val startingVelocity = convertVelocityToAnimationFactor( + valueOnFastVelocity = POP_ON_ACTIVE_MAX_VELOCITY, + valueOnSlowVelocity = POP_ON_ACTIVE_MIN_VELOCITY, + ) + mView.popOffEdge(startingVelocity) } GestureState.INACTIVE -> { @@ -925,9 +933,9 @@ class BackPanelController internal constructor( // so that gesture progress in this state is consistent regardless of entry totalTouchDeltaInactive = params.deactivationTriggerThreshold - val startingVelocity = convertVelocityToSpringStartingVelocity( - valueOnFastVelocity = -1.05f, - valueOnSlowVelocity = -1.50f + val startingVelocity = convertVelocityToAnimationFactor( + valueOnFastVelocity = POP_ON_INACTIVE_MAX_VELOCITY, + valueOnSlowVelocity = POP_ON_INACTIVE_MIN_VELOCITY ) mView.popOffEdge(startingVelocity) @@ -935,7 +943,9 @@ class BackPanelController internal constructor( updateRestingArrowDimens() } GestureState.FLUNG -> { - mainHandler.postDelayed(POP_ON_FLING_DELAY) { mView.popScale(POP_ON_FLING_SCALE) } + mainHandler.postDelayed(POP_ON_FLING_DELAY) { + mView.popScale(POP_ON_FLING_VELOCITY) + } updateRestingArrowDimens() mainHandler.postDelayed(onEndSetCommittedStateListener.runnable, MIN_DURATION_FLING_ANIMATION) @@ -951,7 +961,7 @@ class BackPanelController internal constructor( mainHandler.postDelayed(onEndSetGoneStateListener.runnable, MIN_DURATION_COMMITTED_AFTER_FLING_ANIMATION) } else { - mView.popScale(POP_ON_COMMITTED_SCALE) + mView.popScale(POP_ON_COMMITTED_VELOCITY) mainHandler.postDelayed(onAlphaEndSetGoneStateListener.runnable, MIN_DURATION_COMMITTED_ANIMATION) } @@ -968,11 +978,11 @@ class BackPanelController internal constructor( } } - private fun convertVelocityToSpringStartingVelocity( + private fun convertVelocityToAnimationFactor( valueOnFastVelocity: Float, valueOnSlowVelocity: Float, - fastVelocityBound: Float = 3f, - slowVelocityBound: Float = 0f, + fastVelocityBound: Float = 1f, + slowVelocityBound: Float = 0.5f, ): Float { val factor = velocityTracker?.run { computeCurrentVelocity(PX_PER_MS) diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt index 876c74a9f3e3..182ece7cd328 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt @@ -153,7 +153,7 @@ data class EdgePanelParams(private var resources: Resources) { horizontalTranslation = getDimen(R.dimen.navigation_edge_entry_margin), scale = getDimenFloat(R.dimen.navigation_edge_entry_scale), scalePivotX = getDimen(R.dimen.navigation_edge_pre_threshold_background_width), - horizontalTranslationSpring = createSpring(500f, 0.76f), + horizontalTranslationSpring = createSpring(800f, 0.76f), verticalTranslationSpring = createSpring(30000f, 1f), scaleSpring = createSpring(120f, 0.8f), arrowDimens = ArrowDimens( @@ -205,8 +205,8 @@ data class EdgePanelParams(private var resources: Resources) { activeIndicator = BackIndicatorDimens( horizontalTranslation = getDimen(R.dimen.navigation_edge_active_margin), scale = getDimenFloat(R.dimen.navigation_edge_active_scale), - horizontalTranslationSpring = createSpring(1000f, 0.7f), - scaleSpring = createSpring(450f, 0.39f), + horizontalTranslationSpring = createSpring(1000f, 0.8f), + scaleSpring = createSpring(325f, 0.55f), scalePivotX = getDimen(R.dimen.navigation_edge_active_background_width), arrowDimens = ArrowDimens( length = getDimen(R.dimen.navigation_edge_active_arrow_length), @@ -253,7 +253,7 @@ data class EdgePanelParams(private var resources: Resources) { getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners), farCornerRadius = getDimen(R.dimen.navigation_edge_pre_threshold_far_corners), - widthSpring = createSpring(400f, 0.65f), + widthSpring = createSpring(650f, 1f), heightSpring = createSpring(1500f, 0.45f), farCornerRadiusSpring = createSpring(300f, 1f), edgeCornerRadiusSpring = createSpring(250f, 0.5f), diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt index d9428f897f0d..611c5b987d84 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt @@ -104,6 +104,9 @@ class BackPanelControllerTest : SysuiTestCase() { continueTouch(START_X + touchSlop.toFloat() + 1) // Move again to cross the back trigger threshold continueTouch(START_X + touchSlop + triggerThreshold + 1) + // Wait threshold duration and hold touch past trigger threshold + Thread.sleep((MAX_DURATION_ENTRY_BEFORE_ACTIVE_ANIMATION + 1).toLong()) + continueTouch(START_X + touchSlop + triggerThreshold + 1) assertThat(mBackPanelController.currentState) .isEqualTo(BackPanelController.GestureState.ACTIVE) @@ -114,14 +117,22 @@ class BackPanelControllerTest : SysuiTestCase() { finishTouchActionUp(START_X + touchSlop + triggerThreshold + 1) assertThat(mBackPanelController.currentState) - .isEqualTo(BackPanelController.GestureState.FLUNG) + .isEqualTo(BackPanelController.GestureState.COMMITTED) verify(backCallback).triggerBack() } @Test fun handlesBackCancelled() { startTouch() + // Move once to cross the touch slop continueTouch(START_X + touchSlop.toFloat() + 1) + // Move again to cross the back trigger threshold + continueTouch( + START_X + touchSlop + triggerThreshold - + mBackPanelController.params.deactivationTriggerThreshold + ) + // Wait threshold duration and hold touch before trigger threshold + Thread.sleep((MAX_DURATION_ENTRY_BEFORE_ACTIVE_ANIMATION + 1).toLong()) continueTouch( START_X + touchSlop + triggerThreshold - mBackPanelController.params.deactivationTriggerThreshold |