package com.android.systemui.statusbar import android.animation.Animator import android.animation.ValueAnimator import android.content.Context import android.content.res.Configuration import android.util.MathUtils import android.view.animation.PathInterpolator import com.android.internal.annotations.VisibleForTesting import com.android.systemui.R import com.android.systemui.animation.Interpolators import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.qs.QS import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.phone.ScrimController import com.android.systemui.statusbar.policy.ConfigurationController import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import java.io.PrintWriter class SplitShadeLockScreenOverScroller @AssistedInject constructor( configurationController: ConfigurationController, dumpManager: DumpManager, private val context: Context, private val scrimController: ScrimController, private val statusBarStateController: SysuiStatusBarStateController, @Assisted private val qSProvider: () -> QS, @Assisted private val nsslControllerProvider: () -> NotificationStackScrollLayoutController ) : LockScreenShadeOverScroller { private var releaseOverScrollAnimator: Animator? = null private var transitionToFullShadeDistance = 0 private var releaseOverScrollDuration = 0L private var maxOverScrollAmount = 0 private var previousOverscrollAmount = 0 private val qS: QS get() = qSProvider() private val nsslController: NotificationStackScrollLayoutController get() = nsslControllerProvider() init { updateResources() configurationController.addCallback( object : ConfigurationController.ConfigurationListener { override fun onConfigChanged(newConfig: Configuration?) { updateResources() } }) dumpManager.registerCriticalDumpable("SplitShadeLockscreenOverScroller") { pw, _ -> dump(pw) } } private fun updateResources() { val resources = context.resources transitionToFullShadeDistance = resources.getDimensionPixelSize(R.dimen.lockscreen_shade_full_transition_distance) maxOverScrollAmount = resources.getDimensionPixelSize(R.dimen.lockscreen_shade_max_over_scroll_amount) releaseOverScrollDuration = resources.getInteger(R.integer.lockscreen_shade_over_scroll_release_duration).toLong() } override var expansionDragDownAmount: Float = 0f set(dragDownAmount) { if (field == dragDownAmount) { return } field = dragDownAmount if (shouldOverscroll()) { overScroll(dragDownAmount) } else if (shouldReleaseOverscroll()) { releaseOverScroll() } } private fun shouldOverscroll() = statusBarStateController.state == StatusBarState.KEYGUARD private fun shouldReleaseOverscroll() = !shouldOverscroll() && previousOverscrollAmount != 0 private fun overScroll(dragDownAmount: Float) { val overscrollAmount: Int = calculateOverscrollAmount(dragDownAmount) applyOverscroll(overscrollAmount) previousOverscrollAmount = overscrollAmount } private fun applyOverscroll(overscrollAmount: Int) { qS.setOverScrollAmount(overscrollAmount) scrimController.setNotificationsOverScrollAmount(overscrollAmount) nsslController.setOverScrollAmount(overscrollAmount) } private fun calculateOverscrollAmount(dragDownAmount: Float): Int { val fullHeight: Int = nsslController.height val fullHeightProgress: Float = MathUtils.saturate(dragDownAmount / fullHeight) val overshootStart: Float = transitionToFullShadeDistance / fullHeight.toFloat() val overShootTransitionProgress: Float = Interpolators.getOvershootInterpolation( fullHeightProgress, OVER_SHOOT_AMOUNT, overshootStart) return (overShootTransitionProgress * maxOverScrollAmount).toInt() } private fun releaseOverScroll() { val animator = ValueAnimator.ofInt(previousOverscrollAmount, 0) animator.addUpdateListener { val overScrollAmount = it.animatedValue as Int qS.setOverScrollAmount(overScrollAmount) scrimController.setNotificationsOverScrollAmount(overScrollAmount) nsslController.setOverScrollAmount(overScrollAmount) } animator.interpolator = RELEASE_OVER_SCROLL_INTERPOLATOR animator.duration = releaseOverScrollDuration animator.start() releaseOverScrollAnimator = animator previousOverscrollAmount = 0 } @VisibleForTesting internal fun finishAnimations() { releaseOverScrollAnimator?.end() releaseOverScrollAnimator = null } private fun dump(printWriter: PrintWriter) { printWriter.println( """ SplitShadeLockScreenOverScroller: Resources: transitionToFullShadeDistance: $transitionToFullShadeDistance maxOverScrollAmount: $maxOverScrollAmount releaseOverScrollDuration: $releaseOverScrollDuration State: previousOverscrollAmount: $previousOverscrollAmount expansionDragDownAmount: $expansionDragDownAmount """.trimIndent()) } @AssistedFactory fun interface Factory { fun create( qSProvider: () -> QS, nsslControllerProvider: () -> NotificationStackScrollLayoutController ): SplitShadeLockScreenOverScroller } companion object { private const val OVER_SHOOT_AMOUNT = 0.6f private val RELEASE_OVER_SCROLL_INTERPOLATOR = PathInterpolator(0.17f, 0f, 0f, 1f) } }