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