• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<lambda>null1 package com.android.systemui.animation
2 
3 import android.animation.Animator
4 import android.animation.AnimatorListenerAdapter
5 import android.animation.ValueAnimator
6 import android.app.ActivityManager
7 import android.app.ActivityTaskManager
8 import android.app.AppGlobals
9 import android.app.PendingIntent
10 import android.app.TaskInfo
11 import android.content.Context
12 import android.graphics.Matrix
13 import android.graphics.PorterDuff
14 import android.graphics.PorterDuffXfermode
15 import android.graphics.Rect
16 import android.graphics.RectF
17 import android.graphics.drawable.GradientDrawable
18 import android.os.Looper
19 import android.os.RemoteException
20 import android.util.Log
21 import android.util.MathUtils
22 import android.view.IRemoteAnimationFinishedCallback
23 import android.view.IRemoteAnimationRunner
24 import android.view.RemoteAnimationAdapter
25 import android.view.RemoteAnimationTarget
26 import android.view.SyncRtSurfaceTransactionApplier
27 import android.view.View
28 import android.view.ViewGroup
29 import android.view.WindowManager
30 import android.view.animation.AnimationUtils
31 import android.view.animation.PathInterpolator
32 import com.android.internal.annotations.VisibleForTesting
33 import com.android.internal.policy.ScreenDecorationsUtils
34 import kotlin.math.roundToInt
35 
36 private const val TAG = "ActivityLaunchAnimator"
37 
38 /**
39  * A class that allows activities to be started in a seamless way from a view that is transforming
40  * nicely into the starting window.
41  */
42 class ActivityLaunchAnimator(
43     private val callback: Callback,
44     context: Context
45 ) {
46     companion object {
47         const val ANIMATION_DURATION = 500L
48         private const val ANIMATION_DURATION_FADE_OUT_CONTENT = 150L
49         private const val ANIMATION_DURATION_FADE_IN_WINDOW = 183L
50         private const val ANIMATION_DELAY_FADE_IN_WINDOW = ANIMATION_DURATION_FADE_OUT_CONTENT
51         private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
52         private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L
53         private const val ANIMATION_DELAY_NAV_FADE_IN =
54                 ANIMATION_DURATION - ANIMATION_DURATION_NAV_FADE_IN
55         private const val LAUNCH_TIMEOUT = 1000L
56 
57         @JvmField val CONTENT_FADE_OUT_INTERPOLATOR = PathInterpolator(0f, 0f, 0.2f, 1f)
58         private val WINDOW_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0.6f, 1f)
59         private val NAV_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0f, 1f)
60         private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f)
61 
62         private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
63 
64         /**
65          * Given the [linearProgress] of a launch animation, return the linear progress of the
66          * sub-animation starting [delay] ms after the launch animation and that lasts [duration].
67          */
68         @JvmStatic
69         fun getProgress(linearProgress: Float, delay: Long, duration: Long): Float {
70             return MathUtils.constrain(
71                     (linearProgress * ANIMATION_DURATION - delay) / duration,
72                     0.0f,
73                     1.0f
74             )
75         }
76     }
77 
78     private val packageManager = AppGlobals.getPackageManager()
79 
80     /** The interpolator used for the width, height, Y position and corner radius. */
81     private val animationInterpolator = AnimationUtils.loadInterpolator(context,
82             R.interpolator.launch_animation_interpolator_y)
83 
84     /** The interpolator used for the X position. */
85     private val animationInterpolatorX = AnimationUtils.loadInterpolator(context,
86             R.interpolator.launch_animation_interpolator_x)
87 
88     private val cornerRadii = FloatArray(8)
89 
90     /**
91      * Start an intent and animate the opening window. The intent will be started by running
92      * [intentStarter], which should use the provided [RemoteAnimationAdapter] and return the launch
93      * result. [controller] is responsible from animating the view from which the intent was started
94      * in [Controller.onLaunchAnimationProgress]. No animation will start if there is no window
95      * opening.
96      *
97      * If [controller] is null or [animate] is false, then the intent will be started and no
98      * animation will run.
99      *
100      * If possible, you should pass the [packageName] of the intent that will be started so that
101      * trampoline activity launches will also be animated.
102      *
103      * This method will throw any exception thrown by [intentStarter].
104      */
105     @JvmOverloads
106     fun startIntentWithAnimation(
107         controller: Controller?,
108         animate: Boolean = true,
109         packageName: String? = null,
110         intentStarter: (RemoteAnimationAdapter?) -> Int
111     ) {
112         if (controller == null || !animate) {
113             Log.d(TAG, "Starting intent with no animation")
114             intentStarter(null)
115             controller?.callOnIntentStartedOnMainThread(willAnimate = false)
116             return
117         }
118 
119         Log.d(TAG, "Starting intent with a launch animation")
120         val runner = Runner(controller)
121         val isOnKeyguard = callback.isOnKeyguard()
122 
123         // Pass the RemoteAnimationAdapter to the intent starter only if we are not on the keyguard.
124         val animationAdapter = if (!isOnKeyguard) {
125             RemoteAnimationAdapter(
126                     runner,
127                     ANIMATION_DURATION,
128                     ANIMATION_DURATION - 150 /* statusBarTransitionDelay */
129             )
130         } else {
131             null
132         }
133 
134         // Register the remote animation for the given package to also animate trampoline
135         // activity launches.
136         if (packageName != null && animationAdapter != null) {
137             try {
138                 ActivityTaskManager.getService().registerRemoteAnimationForNextActivityStart(
139                     packageName, animationAdapter)
140             } catch (e: RemoteException) {
141                 Log.w(TAG, "Unable to register the remote animation", e)
142             }
143         }
144 
145         val launchResult = intentStarter(animationAdapter)
146 
147         // Only animate if the app is not already on top and will be opened, unless we are on the
148         // keyguard.
149         val willAnimate =
150                 launchResult == ActivityManager.START_TASK_TO_FRONT ||
151                         launchResult == ActivityManager.START_SUCCESS ||
152                         (launchResult == ActivityManager.START_DELIVERED_TO_TOP && isOnKeyguard)
153 
154         Log.d(TAG, "launchResult=$launchResult willAnimate=$willAnimate isOnKeyguard=$isOnKeyguard")
155         controller.callOnIntentStartedOnMainThread(willAnimate)
156 
157         // If we expect an animation, post a timeout to cancel it in case the remote animation is
158         // never started.
159         if (willAnimate) {
160             runner.postTimeout()
161 
162             // Hide the keyguard using the launch animation instead of the default unlock animation.
163             if (isOnKeyguard) {
164                 callback.hideKeyguardWithAnimation(runner)
165             }
166         }
167     }
168 
169     private fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
170         if (Looper.myLooper() != Looper.getMainLooper()) {
171             this.launchContainer.context.mainExecutor.execute {
172                 this.onIntentStarted(willAnimate)
173             }
174         } else {
175             this.onIntentStarted(willAnimate)
176         }
177     }
178 
179     /**
180      * Same as [startIntentWithAnimation] but allows [intentStarter] to throw a
181      * [PendingIntent.CanceledException] which must then be handled by the caller. This is useful
182      * for Java caller starting a [PendingIntent].
183      *
184      * If possible, you should pass the [packageName] of the intent that will be started so that
185      * trampoline activity launches will also be animated.
186      */
187     @Throws(PendingIntent.CanceledException::class)
188     @JvmOverloads
189     fun startPendingIntentWithAnimation(
190         controller: Controller?,
191         animate: Boolean = true,
192         packageName: String? = null,
193         intentStarter: PendingIntentStarter
194     ) {
195         startIntentWithAnimation(controller, animate, packageName) {
196             intentStarter.startPendingIntent(it)
197         }
198     }
199 
200     /** Create a new animation [Runner] controlled by [controller]. */
201     @VisibleForTesting
202     fun createRunner(controller: Controller): Runner = Runner(controller)
203 
204     interface PendingIntentStarter {
205         /**
206          * Start a pending intent using the provided [animationAdapter] and return the launch
207          * result.
208          */
209         @Throws(PendingIntent.CanceledException::class)
210         fun startPendingIntent(animationAdapter: RemoteAnimationAdapter?): Int
211     }
212 
213     interface Callback {
214         /** Whether we are currently on the keyguard or not. */
215         fun isOnKeyguard(): Boolean
216 
217         /** Hide the keyguard and animate using [runner]. */
218         fun hideKeyguardWithAnimation(runner: IRemoteAnimationRunner)
219 
220         /** Enable/disable window blur so they don't overlap with the window launch animation **/
221         fun setBlursDisabledForAppLaunch(disabled: Boolean)
222 
223         /* Get the background color of [task]. */
224         fun getBackgroundColor(task: TaskInfo): Int
225     }
226 
227     /**
228      * A controller that takes care of applying the animation to an expanding view.
229      *
230      * Note that all callbacks (onXXX methods) are all called on the main thread.
231      */
232     interface Controller {
233         companion object {
234             /**
235              * Return a [Controller] that will animate and expand [view] into the opening window.
236              *
237              * Important: The view must be attached to a [ViewGroup] when calling this function and
238              * during the animation. For safety, this method will return null when it is not.
239              */
240             @JvmStatic
241             fun fromView(view: View, cujType: Int? = null): Controller? {
242                 if (view.parent !is ViewGroup) {
243                     // TODO(b/192194319): Throw instead of just logging.
244                     Log.wtf(
245                         TAG,
246                         "Skipping animation as view $view is not attached to a ViewGroup",
247                         Exception()
248                     )
249                     return null
250                 }
251 
252                 return GhostedViewLaunchAnimatorController(view, cujType)
253             }
254         }
255 
256         /**
257          * The container in which the view that started the intent will be animating together with
258          * the opening window.
259          *
260          * This will be used to:
261          *  - Get the associated [Context].
262          *  - Compute whether we are expanding fully above the current window.
263          *  - Apply surface transactions in sync with RenderThread.
264          *
265          * This container can be changed to force this [Controller] to animate the expanding view
266          * inside a different location, for instance to ensure correct layering during the
267          * animation.
268          */
269         var launchContainer: ViewGroup
270 
271         /**
272          * Return the [State] of the view that will be animated. We will animate from this state to
273          * the final window state.
274          *
275          * Note: This state will be mutated and passed to [onLaunchAnimationProgress] during the
276          * animation.
277          */
278         fun createAnimatorState(): State
279 
280         /**
281          * The intent was started. If [willAnimate] is false, nothing else will happen and the
282          * animation will not be started.
283          */
284         fun onIntentStarted(willAnimate: Boolean) {}
285 
286         /**
287          * The animation started. This is typically used to initialize any additional resource
288          * needed for the animation. [isExpandingFullyAbove] will be true if the window is expanding
289          * fully above the [root view][getRootView].
290          */
291         fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {}
292 
293         /** The animation made progress and the expandable view [state] should be updated. */
294         fun onLaunchAnimationProgress(state: State, progress: Float, linearProgress: Float) {}
295 
296         /**
297          * The animation ended. This will be called *if and only if* [onLaunchAnimationStart] was
298          * called previously. This is typically used to clean up the resources initialized when the
299          * animation was started.
300          */
301         fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {}
302 
303         /**
304          * The animation was cancelled. Note that [onLaunchAnimationEnd] will still be called after
305          * this if the animation was already started, i.e. if [onLaunchAnimationStart] was called
306          * before the cancellation.
307          */
308         fun onLaunchAnimationCancelled() {}
309     }
310 
311     /** The state of an expandable view during an [ActivityLaunchAnimator] animation. */
312     open class State(
313         /** The position of the view in screen space coordinates. */
314         var top: Int,
315         var bottom: Int,
316         var left: Int,
317         var right: Int,
318 
319         var topCornerRadius: Float = 0f,
320         var bottomCornerRadius: Float = 0f
321     ) {
322         private val startTop = top
323         private val startBottom = bottom
324         private val startLeft = left
325         private val startRight = right
326         private val startWidth = width
327         private val startHeight = height
328         val startCenterX = centerX
329         val startCenterY = centerY
330 
331         val width: Int
332             get() = right - left
333 
334         val height: Int
335             get() = bottom - top
336 
337         open val topChange: Int
338             get() = top - startTop
339 
340         open val bottomChange: Int
341             get() = bottom - startBottom
342 
343         val leftChange: Int
344             get() = left - startLeft
345 
346         val rightChange: Int
347             get() = right - startRight
348 
349         val widthRatio: Float
350             get() = width.toFloat() / startWidth
351 
352         val heightRatio: Float
353             get() = height.toFloat() / startHeight
354 
355         val centerX: Float
356             get() = left + width / 2f
357 
358         val centerY: Float
359             get() = top + height / 2f
360 
361         /** Whether the expanded view should be visible or hidden. */
362         var visible: Boolean = true
363     }
364 
365     @VisibleForTesting
366     inner class Runner(private val controller: Controller) : IRemoteAnimationRunner.Stub() {
367         private val launchContainer = controller.launchContainer
368         private val context = launchContainer.context
369         private val transactionApplier = SyncRtSurfaceTransactionApplier(launchContainer)
370         private var animator: ValueAnimator? = null
371 
372         private val matrix = Matrix()
373         private val invertMatrix = Matrix()
374         private var windowCrop = Rect()
375         private var windowCropF = RectF()
376         private var timedOut = false
377         private var cancelled = false
378 
379         // A timeout to cancel the remote animation if it is not started within X milliseconds after
380         // the intent was started.
381         //
382         // Note that this is important to keep this a Runnable (and not a Kotlin lambda), otherwise
383         // it will be automatically converted when posted and we wouldn't be able to remove it after
384         // posting it.
385         private var onTimeout = Runnable { onAnimationTimedOut() }
386 
387         internal fun postTimeout() {
388             launchContainer.postDelayed(onTimeout, LAUNCH_TIMEOUT)
389         }
390 
391         private fun removeTimeout() {
392             launchContainer.removeCallbacks(onTimeout)
393         }
394 
395         override fun onAnimationStart(
396             @WindowManager.TransitionOldType transit: Int,
397             apps: Array<out RemoteAnimationTarget>?,
398             wallpapers: Array<out RemoteAnimationTarget>?,
399             nonApps: Array<out RemoteAnimationTarget>?,
400             iCallback: IRemoteAnimationFinishedCallback?
401         ) {
402             removeTimeout()
403 
404             // The animation was started too late and we already notified the controller that it
405             // timed out.
406             if (timedOut) {
407                 iCallback?.invoke()
408                 return
409             }
410 
411             // This should not happen, but let's make sure we don't start the animation if it was
412             // cancelled before and we already notified the controller.
413             if (cancelled) {
414                 return
415             }
416 
417             context.mainExecutor.execute {
418                 startAnimation(apps, nonApps, iCallback)
419             }
420         }
421 
422         private fun startAnimation(
423             apps: Array<out RemoteAnimationTarget>?,
424             nonApps: Array<out RemoteAnimationTarget>?,
425             iCallback: IRemoteAnimationFinishedCallback?
426         ) {
427             Log.d(TAG, "Remote animation started")
428             val window = apps?.firstOrNull {
429                 it.mode == RemoteAnimationTarget.MODE_OPENING
430             }
431 
432             if (window == null) {
433                 Log.d(TAG, "Aborting the animation as no window is opening")
434                 removeTimeout()
435                 iCallback?.invoke()
436                 controller.onLaunchAnimationCancelled()
437                 return
438             }
439 
440             val navigationBar = nonApps?.firstOrNull {
441                 it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
442             }
443 
444             // Start state.
445             val state = controller.createAnimatorState()
446 
447             val startTop = state.top
448             val startBottom = state.bottom
449             val startLeft = state.left
450             val startRight = state.right
451             val startXCenter = (startLeft + startRight) / 2f
452             val startWidth = startRight - startLeft
453 
454             val startTopCornerRadius = state.topCornerRadius
455             val startBottomCornerRadius = state.bottomCornerRadius
456 
457             // End state.
458             val windowBounds = window.screenSpaceBounds
459             val endTop = windowBounds.top
460             val endBottom = windowBounds.bottom
461             val endLeft = windowBounds.left
462             val endRight = windowBounds.right
463             val endXCenter = (endLeft + endRight) / 2f
464             val endWidth = endRight - endLeft
465 
466             // TODO(b/184121838): Ensure that we are launching on the same screen.
467             val rootViewLocation = launchContainer.locationOnScreen
468             val isExpandingFullyAbove = endTop <= rootViewLocation[1] &&
469                 endBottom >= rootViewLocation[1] + launchContainer.height &&
470                 endLeft <= rootViewLocation[0] &&
471                 endRight >= rootViewLocation[0] + launchContainer.width
472 
473             // TODO(b/184121838): We should somehow get the top and bottom radius of the window.
474             val endRadius = if (isExpandingFullyAbove) {
475                 // Most of the time, expanding fully above the root view means expanding in full
476                 // screen.
477                 ScreenDecorationsUtils.getWindowCornerRadius(context.resources)
478             } else {
479                 // This usually means we are in split screen mode, so 2 out of 4 corners will have
480                 // a radius of 0.
481                 0f
482             }
483 
484             // We add an extra layer with the same color as the app splash screen background color,
485             // which is usually the same color of the app background. We first fade in this layer
486             // to hide the expanding view, then we fade it out with SRC mode to draw a hole in the
487             // launch container and reveal the opening window.
488             val windowBackgroundColor = callback.getBackgroundColor(window.taskInfo)
489             val windowBackgroundLayer = GradientDrawable().apply {
490                 setColor(windowBackgroundColor)
491                 alpha = 0
492             }
493 
494             // Update state.
495             val animator = ValueAnimator.ofFloat(0f, 1f)
496             this.animator = animator
497             animator.duration = ANIMATION_DURATION
498             animator.interpolator = Interpolators.LINEAR
499 
500             val launchContainerOverlay = launchContainer.overlay
501             animator.addListener(object : AnimatorListenerAdapter() {
502                 override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
503                     Log.d(TAG, "Animation started")
504                     callback.setBlursDisabledForAppLaunch(true)
505                     controller.onLaunchAnimationStart(isExpandingFullyAbove)
506 
507                     // Add the drawable to the launch container overlay. Overlays always draw
508                     // drawables after views, so we know that it will be drawn above any view added
509                     // by the controller.
510                     launchContainerOverlay.add(windowBackgroundLayer)
511                 }
512 
513                 override fun onAnimationEnd(animation: Animator?) {
514                     Log.d(TAG, "Animation ended")
515                     callback.setBlursDisabledForAppLaunch(false)
516                     iCallback?.invoke()
517                     controller.onLaunchAnimationEnd(isExpandingFullyAbove)
518                     launchContainerOverlay.remove(windowBackgroundLayer)
519                 }
520             })
521 
522             animator.addUpdateListener { animation ->
523                 if (cancelled) {
524                     return@addUpdateListener
525                 }
526 
527                 val linearProgress = animation.animatedFraction
528                 val progress = animationInterpolator.getInterpolation(linearProgress)
529                 val xProgress = animationInterpolatorX.getInterpolation(linearProgress)
530                 val xCenter = MathUtils.lerp(startXCenter, endXCenter, xProgress)
531                 val halfWidth = lerp(startWidth, endWidth, progress) / 2
532 
533                 state.top = lerp(startTop, endTop, progress).roundToInt()
534                 state.bottom = lerp(startBottom, endBottom, progress).roundToInt()
535                 state.left = (xCenter - halfWidth).roundToInt()
536                 state.right = (xCenter + halfWidth).roundToInt()
537 
538                 state.topCornerRadius = MathUtils.lerp(startTopCornerRadius, endRadius, progress)
539                 state.bottomCornerRadius =
540                     MathUtils.lerp(startBottomCornerRadius, endRadius, progress)
541 
542                 // The expanding view can/should be hidden once it is completely coverred by the
543                 // windowBackgroundLayer.
544                 state.visible =
545                         getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT) < 1
546 
547                 applyStateToWindow(window, state)
548                 applyStateToWindowBackgroundLayer(windowBackgroundLayer, state, linearProgress)
549                 navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
550 
551                 // If we started expanding the view, we make it 1 pixel smaller on all sides to
552                 // avoid artefacts on the corners caused by anti-aliasing of the view background and
553                 // the window background layer.
554                 if (state.top != startTop && state.left != startLeft &&
555                         state.bottom != startBottom && state.right != startRight) {
556                     state.top += 1
557                     state.left += 1
558                     state.right -= 1
559                     state.bottom -= 1
560                 }
561                 controller.onLaunchAnimationProgress(state, progress, linearProgress)
562             }
563 
564             animator.start()
565         }
566 
567         private fun applyStateToWindow(window: RemoteAnimationTarget, state: State) {
568             val screenBounds = window.screenSpaceBounds
569             val centerX = (screenBounds.left + screenBounds.right) / 2f
570             val centerY = (screenBounds.top + screenBounds.bottom) / 2f
571             val width = screenBounds.right - screenBounds.left
572             val height = screenBounds.bottom - screenBounds.top
573 
574             // Scale the window. We use the max of (widthRatio, heightRatio) so that there is no
575             // blank space on any side.
576             val widthRatio = state.width.toFloat() / width
577             val heightRatio = state.height.toFloat() / height
578             val scale = maxOf(widthRatio, heightRatio)
579             matrix.reset()
580             matrix.setScale(scale, scale, centerX, centerY)
581 
582             // Align it to the top and center it in the x-axis.
583             val heightChange = height * scale - height
584             val translationX = state.centerX - centerX
585             val translationY = state.top - screenBounds.top + heightChange / 2f
586             matrix.postTranslate(translationX, translationY)
587 
588             // Crop it. The matrix will also be applied to the crop, so we apply the inverse
589             // operation. Given that we only scale (by factor > 0) then translate, we can assume
590             // that the matrix is invertible.
591             val cropX = state.left.toFloat() - screenBounds.left
592             val cropY = state.top.toFloat() - screenBounds.top
593             windowCropF.set(cropX, cropY, cropX + state.width, cropY + state.height)
594             matrix.invert(invertMatrix)
595             invertMatrix.mapRect(windowCropF)
596             windowCrop.set(
597                 windowCropF.left.roundToInt(),
598                 windowCropF.top.roundToInt(),
599                 windowCropF.right.roundToInt(),
600                 windowCropF.bottom.roundToInt()
601             )
602 
603             // The scale will also be applied to the corner radius, so we divide by the scale to
604             // keep the original radius. We use the max of (topCornerRadius, bottomCornerRadius) to
605             // make sure that the window does not draw itself behind the expanding view. This is
606             // especially important for lock screen animations, where the window is not clipped by
607             // the shade.
608             val cornerRadius = maxOf(state.topCornerRadius, state.bottomCornerRadius) / scale
609             val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(window.leash)
610                 .withAlpha(1f)
611                 .withMatrix(matrix)
612                 .withWindowCrop(windowCrop)
613                 .withLayer(window.prefixOrderIndex)
614                 .withCornerRadius(cornerRadius)
615                 .withVisibility(true)
616                 .build()
617 
618             transactionApplier.scheduleApply(params)
619         }
620 
621         private fun applyStateToWindowBackgroundLayer(
622             drawable: GradientDrawable,
623             state: State,
624             linearProgress: Float
625         ) {
626             // Update position.
627             drawable.setBounds(state.left, state.top, state.right, state.bottom)
628 
629             // Update radius.
630             cornerRadii[0] = state.topCornerRadius
631             cornerRadii[1] = state.topCornerRadius
632             cornerRadii[2] = state.topCornerRadius
633             cornerRadii[3] = state.topCornerRadius
634             cornerRadii[4] = state.bottomCornerRadius
635             cornerRadii[5] = state.bottomCornerRadius
636             cornerRadii[6] = state.bottomCornerRadius
637             cornerRadii[7] = state.bottomCornerRadius
638             drawable.cornerRadii = cornerRadii
639 
640             // We first fade in the background layer to hide the expanding view, then fade it out
641             // with SRC mode to draw a hole punch in the status bar and reveal the opening window.
642             val fadeInProgress = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT)
643             if (fadeInProgress < 1) {
644                 val alpha = CONTENT_FADE_OUT_INTERPOLATOR.getInterpolation(fadeInProgress)
645                 drawable.alpha = (alpha * 0xFF).roundToInt()
646                 drawable.setXfermode(null)
647             } else {
648                 val fadeOutProgress = getProgress(linearProgress,
649                         ANIMATION_DELAY_FADE_IN_WINDOW, ANIMATION_DURATION_FADE_IN_WINDOW)
650                 val alpha = 1 - WINDOW_FADE_IN_INTERPOLATOR.getInterpolation(fadeOutProgress)
651                 drawable.alpha = (alpha * 0xFF).roundToInt()
652                 drawable.setXfermode(SRC_MODE)
653             }
654         }
655 
656         private fun applyStateToNavigationBar(
657             navigationBar: RemoteAnimationTarget,
658             state: State,
659             linearProgress: Float
660         ) {
661             val fadeInProgress = getProgress(linearProgress, ANIMATION_DELAY_NAV_FADE_IN,
662                     ANIMATION_DURATION_NAV_FADE_OUT)
663 
664             val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(navigationBar.leash)
665             if (fadeInProgress > 0) {
666                 matrix.reset()
667                 matrix.setTranslate(
668                     0f, (state.top - navigationBar.sourceContainerBounds.top).toFloat())
669                 windowCrop.set(state.left, 0, state.right, state.height)
670                 params
671                         .withAlpha(NAV_FADE_IN_INTERPOLATOR.getInterpolation(fadeInProgress))
672                         .withMatrix(matrix)
673                         .withWindowCrop(windowCrop)
674                         .withVisibility(true)
675             } else {
676                 val fadeOutProgress = getProgress(linearProgress, 0,
677                         ANIMATION_DURATION_NAV_FADE_OUT)
678                 params.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress))
679             }
680 
681             transactionApplier.scheduleApply(params.build())
682         }
683 
684         private fun onAnimationTimedOut() {
685             if (cancelled) {
686                 return
687             }
688 
689             Log.d(TAG, "Remote animation timed out")
690             timedOut = true
691             controller.onLaunchAnimationCancelled()
692         }
693 
694         override fun onAnimationCancelled() {
695             if (timedOut) {
696                 return
697             }
698 
699             Log.d(TAG, "Remote animation was cancelled")
700             cancelled = true
701             removeTimeout()
702             context.mainExecutor.execute {
703                 animator?.cancel()
704                 controller.onLaunchAnimationCancelled()
705             }
706         }
707 
708         private fun IRemoteAnimationFinishedCallback.invoke() {
709             try {
710                 onAnimationFinished()
711             } catch (e: RemoteException) {
712                 e.printStackTrace()
713             }
714         }
715 
716         private fun lerp(start: Int, stop: Int, amount: Float): Float {
717             return MathUtils.lerp(start.toFloat(), stop.toFloat(), amount)
718         }
719     }
720 }
721