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