1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.animation.back 18 19 import android.util.TimeUtils 20 import android.view.Choreographer 21 import android.view.MotionEvent 22 import android.view.MotionEvent.ACTION_MOVE 23 import android.view.VelocityTracker 24 import android.view.animation.Interpolator 25 import android.window.BackEvent 26 import android.window.OnBackAnimationCallback 27 import com.android.app.animation.Interpolators 28 import com.android.internal.dynamicanimation.animation.DynamicAnimation 29 import com.android.internal.dynamicanimation.animation.FlingAnimation 30 import com.android.internal.dynamicanimation.animation.FloatValueHolder 31 import com.android.window.flags.Flags.predictiveBackTimestampApi 32 33 private const val FLING_FRICTION = 6f 34 private const val SCALE_FACTOR = 100f 35 36 /** 37 * Enhanced [OnBackAnimationCallback] with automatic fling animation and interpolated progress. 38 * 39 * Simplifies back gesture handling by animating flings and emitting processed events through 40 * `compat` functions. Customize progress interpolation with an optional [Interpolator]. 41 * 42 * @param progressInterpolator [Interpolator] for progress, defaults to 43 * [Interpolators.BACK_GESTURE]. 44 */ 45 abstract class FlingOnBackAnimationCallback( 46 val progressInterpolator: Interpolator = Interpolators.BACK_GESTURE 47 ) : OnBackAnimationCallback { 48 49 private val velocityTracker = VelocityTracker.obtain() 50 private var lastBackEvent: BackEvent? = null 51 private var downTime: Long? = null 52 53 private var backInvokedFlingAnim: FlingAnimation? = null 54 private val backInvokedFlingUpdateListener = 55 DynamicAnimation.OnAnimationUpdateListener { _, progress: Float, _ -> 56 lastBackEvent?.let { 57 val backEvent = 58 BackEvent( 59 it.touchX, 60 it.touchY, 61 progress / SCALE_FACTOR, 62 it.swipeEdge, 63 it.frameTimeMillis, 64 ) 65 onBackProgressedCompat(backEvent) 66 } 67 } 68 private val backInvokedFlingEndListener = 69 DynamicAnimation.OnAnimationEndListener { _, _, _, _ -> 70 onBackInvokedCompat() 71 reset() 72 } 73 74 abstract fun onBackStartedCompat(backEvent: BackEvent) 75 76 abstract fun onBackProgressedCompat(backEvent: BackEvent) 77 78 abstract fun onBackInvokedCompat() 79 80 abstract fun onBackCancelledCompat() 81 82 final override fun onBackStarted(backEvent: BackEvent) { 83 if (backInvokedFlingAnim != null) { 84 // This should never happen but let's call onBackInvokedCompat() just in case there is 85 // still a fling animation in progress 86 onBackInvokedCompat() 87 } 88 reset() 89 if (predictiveBackTimestampApi()) { 90 downTime = backEvent.frameTimeMillis 91 } 92 onBackStartedCompat(backEvent) 93 } 94 95 final override fun onBackProgressed(backEvent: BackEvent) { 96 val interpolatedProgress = progressInterpolator.getInterpolation(backEvent.progress) 97 if (predictiveBackTimestampApi()) { 98 downTime?.let { downTime -> 99 velocityTracker.addMovement( 100 MotionEvent.obtain( 101 /* downTime */ downTime, 102 /* eventTime */ backEvent.frameTimeMillis, 103 /* action */ ACTION_MOVE, 104 /* x */ interpolatedProgress * SCALE_FACTOR, 105 /* y */ 0f, 106 /* metaState */ 0, 107 ) 108 ) 109 } 110 lastBackEvent = 111 BackEvent( 112 backEvent.touchX, 113 backEvent.touchY, 114 interpolatedProgress, 115 backEvent.swipeEdge, 116 backEvent.frameTimeMillis, 117 ) 118 } else { 119 lastBackEvent = 120 BackEvent( 121 backEvent.touchX, 122 backEvent.touchY, 123 interpolatedProgress, 124 backEvent.swipeEdge, 125 ) 126 } 127 lastBackEvent?.let { onBackProgressedCompat(it) } 128 } 129 130 final override fun onBackInvoked() { 131 if (predictiveBackTimestampApi() && lastBackEvent != null) { 132 velocityTracker.computeCurrentVelocity(1000) 133 backInvokedFlingAnim = 134 FlingAnimation(FloatValueHolder()) 135 .setStartValue((lastBackEvent?.progress ?: 0f) * SCALE_FACTOR) 136 .setFriction(FLING_FRICTION) 137 .setStartVelocity(velocityTracker.xVelocity) 138 .setMinValue(0f) 139 .setMaxValue(SCALE_FACTOR) 140 .also { 141 it.addUpdateListener(backInvokedFlingUpdateListener) 142 it.addEndListener(backInvokedFlingEndListener) 143 it.start() 144 // do an animation-frame immediately to prevent idle frame 145 it.doAnimationFrame( 146 Choreographer.getInstance().lastFrameTimeNanos / TimeUtils.NANOS_PER_MS 147 ) 148 } 149 } else { 150 onBackInvokedCompat() 151 reset() 152 } 153 } 154 155 final override fun onBackCancelled() { 156 onBackCancelledCompat() 157 reset() 158 } 159 160 private fun reset() { 161 velocityTracker.clear() 162 backInvokedFlingAnim?.removeEndListener(backInvokedFlingEndListener) 163 backInvokedFlingAnim?.removeUpdateListener(backInvokedFlingUpdateListener) 164 lastBackEvent = null 165 backInvokedFlingAnim = null 166 downTime = null 167 } 168 } 169