1 package com.android.systemui.unfold.progress
2 
3 import android.os.Trace
4 import android.util.Log
5 import androidx.dynamicanimation.animation.FloatPropertyCompat
6 import androidx.dynamicanimation.animation.SpringAnimation
7 import androidx.dynamicanimation.animation.SpringForce
8 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
9 
10 /**
11  * Makes progress received from other processes resilient to jank.
12  *
13  * Sender and receiver processes might have different frame-rates. If the sending process is
14  * dropping a frame due to jank (or generally because it's main thread is too busy), we don't want
15  * the receiving process to drop progress frames as well. For this reason, a spring animator pass
16  * (with very high stiffness) is applied to the incoming progress. This adds a small delay to the
17  * progress (~30ms), but guarantees an always smooth animation on the receiving end.
18  */
19 class UnfoldRemoteFilter(
20     private val listener: UnfoldTransitionProgressProvider.TransitionProgressListener
21 ) : UnfoldTransitionProgressProvider.TransitionProgressListener {
22 
23     private val springAnimation =
<lambda>null24         SpringAnimation(this, AnimationProgressProperty).apply {
25             spring =
26                 SpringForce().apply {
27                     dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
28                     stiffness = 100_000f
29                     finalPosition = 1.0f
30                 }
31             setMinValue(0f)
32             setMaxValue(1f)
33             minimumVisibleChange = 0.001f
34         }
35 
36     private var inProgress = false
37     private var receivedProgressEvent = false
38 
39     private var processedProgress: Float = 1.0f
40         set(newProgress) {
41             if (inProgress) {
<lambda>null42                 logCounter({ "$TAG#filtered_progress" }, newProgress)
43                 listener.onTransitionProgress(newProgress)
44             }
45             field = newProgress
46         }
47 
onTransitionStartednull48     override fun onTransitionStarted() {
49         listener.onTransitionStarted()
50         inProgress = true
51     }
52 
onTransitionProgressnull53     override fun onTransitionProgress(progress: Float) {
54         logCounter({ "$TAG#plain_remote_progress" }, progress)
55         if (inProgress) {
56             if (receivedProgressEvent) {
57                 // We have received at least one progress event, animate from the previous
58                 // progress to the current
59                 springAnimation.animateToFinalPosition(progress)
60             } else {
61                 // This is the first progress event after starting the animation, send it
62                 // straightaway and set the spring value without animating it
63                 processedProgress = progress
64                 receivedProgressEvent = true
65             }
66         } else {
67             Log.e(TAG, "Progress received while not in progress.")
68         }
69     }
70 
onTransitionFinishednull71     override fun onTransitionFinished() {
72         inProgress = false
73         receivedProgressEvent = false
74         listener.onTransitionFinished()
75     }
76 
77     private object AnimationProgressProperty :
78         FloatPropertyCompat<UnfoldRemoteFilter>("UnfoldRemoteFilter") {
79 
setValuenull80         override fun setValue(provider: UnfoldRemoteFilter, value: Float) {
81             provider.processedProgress = value
82         }
83 
getValuenull84         override fun getValue(provider: UnfoldRemoteFilter): Float = provider.processedProgress
85     }
86     private fun logCounter(name: () -> String, progress: Float) {
87         if (DEBUG) {
88             Trace.setCounter(name(), (progress * 100).toLong())
89         }
90     }
91 }
92 
93 private val TAG = "UnfoldRemoteFilter"
94 private val DEBUG = false
95