<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