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 = 24 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) { 42 logCounter({ "$TAG#filtered_progress" }, newProgress) 43 listener.onTransitionProgress(newProgress) 44 } else { 45 Log.e(TAG, "Filtered progress received received while animation not in progress.") 46 } 47 field = newProgress 48 } 49 50 override fun onTransitionStarted() { 51 listener.onTransitionStarted() 52 inProgress = true 53 } 54 55 override fun onTransitionProgress(progress: Float) { 56 logCounter({ "$TAG#plain_remote_progress" }, progress) 57 if (inProgress) { 58 if (receivedProgressEvent) { 59 // We have received at least one progress event, animate from the previous 60 // progress to the current 61 springAnimation.animateToFinalPosition(progress) 62 } else { 63 // This is the first progress event after starting the animation, send it 64 // straightaway and set the spring value without animating it 65 processedProgress = progress 66 receivedProgressEvent = true 67 } 68 } else { 69 Log.e(TAG, "Progress received while not in progress.") 70 } 71 } 72 73 override fun onTransitionFinished() { 74 inProgress = false 75 receivedProgressEvent = false 76 listener.onTransitionFinished() 77 } 78 79 private object AnimationProgressProperty : 80 FloatPropertyCompat<UnfoldRemoteFilter>("UnfoldRemoteFilter") { 81 82 override fun setValue(provider: UnfoldRemoteFilter, value: Float) { 83 provider.processedProgress = value 84 } 85 86 override fun getValue(provider: UnfoldRemoteFilter): Float = provider.processedProgress 87 } 88 private fun logCounter(name: () -> String, progress: Float) { 89 if (DEBUG) { 90 Trace.setCounter(name(), (progress * 100).toLong()) 91 } 92 } 93 } 94 95 private val TAG = "UnfoldRemoteFilter" 96 private val DEBUG = false 97