1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.animation
18 
19 import android.app.ActivityManager
20 import android.app.ActivityTaskManager
21 import android.app.PendingIntent
22 import android.app.TaskInfo
23 import android.graphics.Matrix
24 import android.graphics.Path
25 import android.graphics.Rect
26 import android.graphics.RectF
27 import android.os.Build
28 import android.os.Handler
29 import android.os.Looper
30 import android.os.RemoteException
31 import android.util.Log
32 import android.view.IRemoteAnimationFinishedCallback
33 import android.view.IRemoteAnimationRunner
34 import android.view.RemoteAnimationAdapter
35 import android.view.RemoteAnimationTarget
36 import android.view.SyncRtSurfaceTransactionApplier
37 import android.view.View
38 import android.view.ViewGroup
39 import android.view.WindowManager
40 import android.view.animation.Interpolator
41 import android.view.animation.PathInterpolator
42 import androidx.annotation.AnyThread
43 import androidx.annotation.BinderThread
44 import androidx.annotation.UiThread
45 import com.android.app.animation.Interpolators
46 import com.android.internal.annotations.VisibleForTesting
47 import com.android.internal.policy.ScreenDecorationsUtils
48 import kotlin.math.roundToInt
49 
50 private const val TAG = "ActivityLaunchAnimator"
51 
52 /**
53  * A class that allows activities to be started in a seamless way from a view that is transforming
54  * nicely into the starting window.
55  */
56 class ActivityLaunchAnimator(
57     /** The animator used when animating a View into an app. */
58     private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR,
59 
60     /** The animator used when animating a Dialog into an app. */
61     // TODO(b/218989950): Remove this animator and instead set the duration of the dim fade out to
62     // TIMINGS.contentBeforeFadeOutDuration.
63     private val dialogToAppAnimator: LaunchAnimator = DEFAULT_DIALOG_TO_APP_ANIMATOR,
64 
65     /**
66      * Whether we should disable the WindowManager timeout. This should be set to true in tests
67      * only.
68      */
69     // TODO(b/301385865): Remove this flag.
70     private val disableWmTimeout: Boolean = false,
71 ) {
72     companion object {
73         /** The timings when animating a View into an app. */
74         @JvmField
75         val TIMINGS =
76             LaunchAnimator.Timings(
77                 totalDuration = 500L,
78                 contentBeforeFadeOutDelay = 0L,
79                 contentBeforeFadeOutDuration = 150L,
80                 contentAfterFadeInDelay = 150L,
81                 contentAfterFadeInDuration = 183L
82             )
83 
84         /**
85          * The timings when animating a Dialog into an app. We need to wait at least 200ms before
86          * showing the app (which is under the dialog window) so that the dialog window dim is fully
87          * faded out, to avoid flicker.
88          */
89         val DIALOG_TIMINGS =
90             TIMINGS.copy(contentBeforeFadeOutDuration = 200L, contentAfterFadeInDelay = 200L)
91 
92         /** The interpolators when animating a View or a dialog into an app. */
93         val INTERPOLATORS =
94             LaunchAnimator.Interpolators(
95                 positionInterpolator = Interpolators.EMPHASIZED,
96                 positionXInterpolator = createPositionXInterpolator(),
97                 contentBeforeFadeOutInterpolator = Interpolators.LINEAR_OUT_SLOW_IN,
98                 contentAfterFadeInInterpolator = PathInterpolator(0f, 0f, 0.6f, 1f)
99             )
100 
101         // TODO(b/288507023): Remove this flag.
102         @JvmField val DEBUG_LAUNCH_ANIMATION = Build.IS_DEBUGGABLE
103 
104         private val DEFAULT_LAUNCH_ANIMATOR = LaunchAnimator(TIMINGS, INTERPOLATORS)
105         private val DEFAULT_DIALOG_TO_APP_ANIMATOR = LaunchAnimator(DIALOG_TIMINGS, INTERPOLATORS)
106 
107         /** Durations & interpolators for the navigation bar fading in & out. */
108         private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
109         private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L
110         private val ANIMATION_DELAY_NAV_FADE_IN =
111             TIMINGS.totalDuration - ANIMATION_DURATION_NAV_FADE_IN
112 
113         private val NAV_FADE_IN_INTERPOLATOR = Interpolators.STANDARD_DECELERATE
114         private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f)
115 
116         /** The time we wait before timing out the remote animation after starting the intent. */
117         private const val LAUNCH_TIMEOUT = 1000L
118 
119         private fun createPositionXInterpolator(): Interpolator {
120             val path =
121                 Path().apply {
122                     moveTo(0f, 0f)
123                     cubicTo(0.1217f, 0.0462f, 0.15f, 0.4686f, 0.1667f, 0.66f)
124                     cubicTo(0.1834f, 0.8878f, 0.1667f, 1f, 1f, 1f)
125                 }
126             return PathInterpolator(path)
127         }
128     }
129 
130     /**
131      * The callback of this animator. This should be set before any call to
132      * [start(Pending)IntentWithAnimation].
133      */
134     var callback: Callback? = null
135 
136     /** The set of [Listener] that should be notified of any animation started by this animator. */
137     private val listeners = LinkedHashSet<Listener>()
138 
139     /** Top-level listener that can be used to notify all registered [listeners]. */
140     private val lifecycleListener =
141         object : Listener {
142             override fun onLaunchAnimationStart() {
143                 listeners.forEach { it.onLaunchAnimationStart() }
144             }
145 
146             override fun onLaunchAnimationEnd() {
147                 listeners.forEach { it.onLaunchAnimationEnd() }
148             }
149 
150             override fun onLaunchAnimationProgress(linearProgress: Float) {
151                 listeners.forEach { it.onLaunchAnimationProgress(linearProgress) }
152             }
153 
154             override fun onLaunchAnimationCancelled() {
155                 listeners.forEach { it.onLaunchAnimationCancelled() }
156             }
157         }
158 
159     /**
160      * Start an intent and animate the opening window. The intent will be started by running
161      * [intentStarter], which should use the provided [RemoteAnimationAdapter] and return the launch
162      * result. [controller] is responsible from animating the view from which the intent was started
163      * in [Controller.onLaunchAnimationProgress]. No animation will start if there is no window
164      * opening.
165      *
166      * If [controller] is null or [animate] is false, then the intent will be started and no
167      * animation will run.
168      *
169      * If possible, you should pass the [packageName] of the intent that will be started so that
170      * trampoline activity launches will also be animated.
171      *
172      * If the device is currently locked, the user will have to unlock it before the intent is
173      * started unless [showOverLockscreen] is true. In that case, the activity will be started
174      * directly over the lockscreen.
175      *
176      * This method will throw any exception thrown by [intentStarter].
177      */
178     @JvmOverloads
179     fun startIntentWithAnimation(
180         controller: Controller?,
181         animate: Boolean = true,
182         packageName: String? = null,
183         showOverLockscreen: Boolean = false,
184         intentStarter: (RemoteAnimationAdapter?) -> Int
185     ) {
186         if (controller == null || !animate) {
187             Log.i(TAG, "Starting intent with no animation")
188             intentStarter(null)
189             controller?.callOnIntentStartedOnMainThread(willAnimate = false)
190             return
191         }
192 
193         val callback =
194             this.callback
195                 ?: throw IllegalStateException(
196                     "ActivityLaunchAnimator.callback must be set before using this animator"
197                 )
198         val runner = createRunner(controller)
199         val runnerDelegate = runner.delegate!!
200         val hideKeyguardWithAnimation = callback.isOnKeyguard() && !showOverLockscreen
201 
202         // Pass the RemoteAnimationAdapter to the intent starter only if we are not hiding the
203         // keyguard with the animation
204         val animationAdapter =
205             if (!hideKeyguardWithAnimation) {
206                 RemoteAnimationAdapter(
207                     runner,
208                     TIMINGS.totalDuration,
209                     TIMINGS.totalDuration - 150 /* statusBarTransitionDelay */
210                 )
211             } else {
212                 null
213             }
214 
215         // Register the remote animation for the given package to also animate trampoline
216         // activity launches.
217         if (packageName != null && animationAdapter != null) {
218             try {
219                 ActivityTaskManager.getService()
220                     .registerRemoteAnimationForNextActivityStart(
221                         packageName,
222                         animationAdapter,
223                         null /* launchCookie */
224                     )
225             } catch (e: RemoteException) {
226                 Log.w(TAG, "Unable to register the remote animation", e)
227             }
228         }
229 
230         val launchResult = intentStarter(animationAdapter)
231 
232         // Only animate if the app is not already on top and will be opened, unless we are on the
233         // keyguard.
234         val willAnimate =
235             launchResult == ActivityManager.START_TASK_TO_FRONT ||
236                 launchResult == ActivityManager.START_SUCCESS ||
237                 (launchResult == ActivityManager.START_DELIVERED_TO_TOP &&
238                     hideKeyguardWithAnimation)
239 
240         Log.i(
241             TAG,
242             "launchResult=$launchResult willAnimate=$willAnimate " +
243                 "hideKeyguardWithAnimation=$hideKeyguardWithAnimation"
244         )
245         controller.callOnIntentStartedOnMainThread(willAnimate)
246 
247         // If we expect an animation, post a timeout to cancel it in case the remote animation is
248         // never started.
249         if (willAnimate) {
250             runnerDelegate.postTimeout()
251 
252             // Hide the keyguard using the launch animation instead of the default unlock animation.
253             if (hideKeyguardWithAnimation) {
254                 callback.hideKeyguardWithAnimation(runner)
255             }
256         } else {
257             // We need to make sure delegate references are dropped to avoid memory leaks.
258             runner.dispose()
259         }
260     }
261 
262     private fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
263         if (Looper.myLooper() != Looper.getMainLooper()) {
264             this.launchContainer.context.mainExecutor.execute {
265                 callOnIntentStartedOnMainThread(willAnimate)
266             }
267         } else {
268             if (DEBUG_LAUNCH_ANIMATION) {
269                 Log.d(
270                     TAG,
271                     "Calling controller.onIntentStarted(willAnimate=$willAnimate) " +
272                         "[controller=$this]"
273                 )
274             }
275             this.onIntentStarted(willAnimate)
276         }
277     }
278 
279     /**
280      * Same as [startIntentWithAnimation] but allows [intentStarter] to throw a
281      * [PendingIntent.CanceledException] which must then be handled by the caller. This is useful
282      * for Java caller starting a [PendingIntent].
283      *
284      * If possible, you should pass the [packageName] of the intent that will be started so that
285      * trampoline activity launches will also be animated.
286      */
287     @Throws(PendingIntent.CanceledException::class)
288     @JvmOverloads
289     fun startPendingIntentWithAnimation(
290         controller: Controller?,
291         animate: Boolean = true,
292         packageName: String? = null,
293         intentStarter: PendingIntentStarter
294     ) {
295         startIntentWithAnimation(controller, animate, packageName) {
296             intentStarter.startPendingIntent(it)
297         }
298     }
299 
300     /** Add a [Listener] that can listen to launch animations. */
301     fun addListener(listener: Listener) {
302         listeners.add(listener)
303     }
304 
305     /** Remove a [Listener]. */
306     fun removeListener(listener: Listener) {
307         listeners.remove(listener)
308     }
309 
310     /** Create a new animation [Runner] controlled by [controller]. */
311     @VisibleForTesting
312     fun createRunner(controller: Controller): Runner {
313         // Make sure we use the modified timings when animating a dialog into an app.
314         val launchAnimator =
315             if (controller.isDialogLaunch) {
316                 dialogToAppAnimator
317             } else {
318                 launchAnimator
319             }
320 
321         return Runner(controller, callback!!, launchAnimator, lifecycleListener)
322     }
323 
324     interface PendingIntentStarter {
325         /**
326          * Start a pending intent using the provided [animationAdapter] and return the launch
327          * result.
328          */
329         @Throws(PendingIntent.CanceledException::class)
330         fun startPendingIntent(animationAdapter: RemoteAnimationAdapter?): Int
331     }
332 
333     interface Callback {
334         /** Whether we are currently on the keyguard or not. */
335         @JvmDefault fun isOnKeyguard(): Boolean = false
336 
337         /** Hide the keyguard and animate using [runner]. */
338         @JvmDefault
339         fun hideKeyguardWithAnimation(runner: IRemoteAnimationRunner) {
340             throw UnsupportedOperationException()
341         }
342 
343         /* Get the background color of [task]. */
344         fun getBackgroundColor(task: TaskInfo): Int
345     }
346 
347     interface Listener {
348         /** Called when an activity launch animation started. */
349         @JvmDefault fun onLaunchAnimationStart() {}
350 
351         /**
352          * Called when an activity launch animation is finished. This will be called if and only if
353          * [onLaunchAnimationStart] was called earlier.
354          */
355         @JvmDefault fun onLaunchAnimationEnd() {}
356 
357         /**
358          * The animation was cancelled. Note that [onLaunchAnimationEnd] will still be called after
359          * this if the animation was already started, i.e. if [onLaunchAnimationStart] was called
360          * before the cancellation.
361          */
362         fun onLaunchAnimationCancelled() {}
363 
364         /** Called when an activity launch animation made progress. */
365         @JvmDefault fun onLaunchAnimationProgress(linearProgress: Float) {}
366     }
367 
368     /**
369      * A controller that takes care of applying the animation to an expanding view.
370      *
371      * Note that all callbacks (onXXX methods) are all called on the main thread.
372      */
373     interface Controller : LaunchAnimator.Controller {
374         companion object {
375             /**
376              * Return a [Controller] that will animate and expand [view] into the opening window.
377              *
378              * Important: The view must be attached to a [ViewGroup] when calling this function and
379              * during the animation. For safety, this method will return null when it is not. The
380              * view must also implement [LaunchableView], otherwise this method will throw.
381              *
382              * Note: The background of [view] should be a (rounded) rectangle so that it can be
383              * properly animated.
384              */
385             @JvmStatic
386             fun fromView(view: View, cujType: Int? = null): Controller? {
387                 // Make sure the View we launch from implements LaunchableView to avoid visibility
388                 // issues.
389                 if (view !is LaunchableView) {
390                     throw IllegalArgumentException(
391                         "An ActivityLaunchAnimator.Controller was created from a View that does " +
392                             "not implement LaunchableView. This can lead to subtle bugs where the" +
393                             " visibility of the View we are launching from is not what we expected."
394                     )
395                 }
396 
397                 if (view.parent !is ViewGroup) {
398                     Log.e(
399                         TAG,
400                         "Skipping animation as view $view is not attached to a ViewGroup",
401                         Exception()
402                     )
403                     return null
404                 }
405 
406                 return GhostedViewLaunchAnimatorController(view, cujType)
407             }
408         }
409 
410         /**
411          * Whether this controller is controlling a dialog launch. This will be used to adapt the
412          * timings, making sure we don't show the app until the dialog dim had the time to fade out.
413          */
414         // TODO(b/218989950): Remove this.
415         val isDialogLaunch: Boolean
416             get() = false
417 
418         /**
419          * Whether the expandable controller by this [Controller] is below the launching window that
420          * is going to be animated.
421          *
422          * This should be `false` when launching an app from the shade or status bar, given that
423          * they are drawn above all apps. This is usually `true` when using this launcher in a
424          * normal app or a launcher, that are drawn below the animating activity/window.
425          */
426         val isBelowAnimatingWindow: Boolean
427             get() = false
428 
429         /**
430          * The intent was started. If [willAnimate] is false, nothing else will happen and the
431          * animation will not be started.
432          */
433         fun onIntentStarted(willAnimate: Boolean) {}
434 
435         /**
436          * The animation was cancelled. Note that [onLaunchAnimationEnd] will still be called after
437          * this if the animation was already started, i.e. if [onLaunchAnimationStart] was called
438          * before the cancellation.
439          *
440          * If this launch animation affected the occlusion state of the keyguard, WM will provide us
441          * with [newKeyguardOccludedState] so that we can set the occluded state appropriately.
442          */
443         fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {}
444     }
445 
446     /**
447      * Invokes [onAnimationComplete] when animation is either cancelled or completed. Delegates all
448      * events to the passed [delegate].
449      */
450     @VisibleForTesting
451     inner class DelegatingAnimationCompletionListener(
452         private val delegate: Listener?,
453         private val onAnimationComplete: () -> Unit
454     ) : Listener {
455         var cancelled = false
456 
457         override fun onLaunchAnimationStart() {
458             delegate?.onLaunchAnimationStart()
459         }
460 
461         override fun onLaunchAnimationProgress(linearProgress: Float) {
462             delegate?.onLaunchAnimationProgress(linearProgress)
463         }
464 
465         override fun onLaunchAnimationEnd() {
466             delegate?.onLaunchAnimationEnd()
467             if (!cancelled) {
468                 onAnimationComplete.invoke()
469             }
470         }
471 
472         override fun onLaunchAnimationCancelled() {
473             cancelled = true
474             delegate?.onLaunchAnimationCancelled()
475             onAnimationComplete.invoke()
476         }
477     }
478 
479     @VisibleForTesting
480     inner class Runner(
481         controller: Controller,
482         callback: Callback,
483         /** The animator to use to animate the window launch. */
484         launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR,
485         /** Listener for animation lifecycle events. */
486         listener: Listener? = null
487     ) : IRemoteAnimationRunner.Stub() {
488         private val context = controller.launchContainer.context
489 
490         // This is being passed across IPC boundaries and cycles (through PendingIntentRecords,
491         // etc.) are possible. So we need to make sure we drop any references that might
492         // transitively cause leaks when we're done with animation.
493         @VisibleForTesting var delegate: AnimationDelegate?
494 
495         init {
496             delegate =
497                 AnimationDelegate(
498                     controller,
499                     callback,
500                     DelegatingAnimationCompletionListener(listener, this::dispose),
501                     launchAnimator,
502                     disableWmTimeout
503                 )
504         }
505 
506         @BinderThread
507         override fun onAnimationStart(
508             transit: Int,
509             apps: Array<out RemoteAnimationTarget>?,
510             wallpapers: Array<out RemoteAnimationTarget>?,
511             nonApps: Array<out RemoteAnimationTarget>?,
512             finishedCallback: IRemoteAnimationFinishedCallback?
513         ) {
514             val delegate = delegate
515             context.mainExecutor.execute {
516                 if (delegate == null) {
517                     Log.i(TAG, "onAnimationStart called after completion")
518                     // Animation started too late and timed out already. We need to still
519                     // signal back that we're done with it.
520                     finishedCallback?.onAnimationFinished()
521                 } else {
522                     delegate.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback)
523                 }
524             }
525         }
526 
527         @BinderThread
528         override fun onAnimationCancelled() {
529             val delegate = delegate
530             context.mainExecutor.execute {
531                 delegate ?: Log.wtf(TAG, "onAnimationCancelled called after completion")
532                 delegate?.onAnimationCancelled()
533             }
534         }
535 
536         @AnyThread
537         fun dispose() {
538             // Drop references to animation controller once we're done with the animation
539             // to avoid leaking.
540             context.mainExecutor.execute { delegate = null }
541         }
542     }
543 
544     class AnimationDelegate
545     @JvmOverloads
546     constructor(
547         private val controller: Controller,
548         private val callback: Callback,
549         /** Listener for animation lifecycle events. */
550         private val listener: Listener? = null,
551         /** The animator to use to animate the window launch. */
552         private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR,
553 
554         /**
555          * Whether we should disable the WindowManager timeout. This should be set to true in tests
556          * only.
557          */
558         // TODO(b/301385865): Remove this flag.
559         disableWmTimeout: Boolean = false,
560     ) : RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> {
561         private val launchContainer = controller.launchContainer
562         private val context = launchContainer.context
563         private val transactionApplierView =
564             controller.openingWindowSyncView ?: controller.launchContainer
565         private val transactionApplier = SyncRtSurfaceTransactionApplier(transactionApplierView)
566         private val timeoutHandler =
567             if (!disableWmTimeout) {
568                 Handler(Looper.getMainLooper())
569             } else {
570                 null
571             }
572 
573         private val matrix = Matrix()
574         private val invertMatrix = Matrix()
575         private var windowCrop = Rect()
576         private var windowCropF = RectF()
577         private var timedOut = false
578         private var cancelled = false
579         private var animation: LaunchAnimator.Animation? = null
580 
581         // A timeout to cancel the remote animation if it is not started within X milliseconds after
582         // the intent was started.
583         //
584         // Note that this is important to keep this a Runnable (and not a Kotlin lambda), otherwise
585         // it will be automatically converted when posted and we wouldn't be able to remove it after
586         // posting it.
587         private var onTimeout = Runnable { onAnimationTimedOut() }
588 
589         @UiThread
590         internal fun postTimeout() {
591             timeoutHandler?.postDelayed(onTimeout, LAUNCH_TIMEOUT)
592         }
593 
594         private fun removeTimeout() {
595             timeoutHandler?.removeCallbacks(onTimeout)
596         }
597 
598         @UiThread
599         override fun onAnimationStart(
600             @WindowManager.TransitionOldType transit: Int,
601             apps: Array<out RemoteAnimationTarget>?,
602             wallpapers: Array<out RemoteAnimationTarget>?,
603             nonApps: Array<out RemoteAnimationTarget>?,
604             callback: IRemoteAnimationFinishedCallback?
605         ) {
606             removeTimeout()
607 
608             // The animation was started too late and we already notified the controller that it
609             // timed out.
610             if (timedOut) {
611                 callback?.invoke()
612                 return
613             }
614 
615             // This should not happen, but let's make sure we don't start the animation if it was
616             // cancelled before and we already notified the controller.
617             if (cancelled) {
618                 return
619             }
620 
621             startAnimation(apps, nonApps, callback)
622         }
623 
624         private fun findRootTaskIfPossible(
625             apps: Array<out RemoteAnimationTarget>?
626         ): RemoteAnimationTarget? {
627             if (apps == null) {
628                 return null
629             }
630             var candidate: RemoteAnimationTarget? = null
631             for (it in apps) {
632                 if (it.mode == RemoteAnimationTarget.MODE_OPENING) {
633                     if (it.taskInfo != null && !it.hasAnimatingParent) {
634                         return it
635                     }
636                     if (candidate == null) {
637                         candidate = it
638                     }
639                 }
640             }
641             return candidate
642         }
643 
644         private fun startAnimation(
645             apps: Array<out RemoteAnimationTarget>?,
646             nonApps: Array<out RemoteAnimationTarget>?,
647             iCallback: IRemoteAnimationFinishedCallback?
648         ) {
649             if (LaunchAnimator.DEBUG) {
650                 Log.d(TAG, "Remote animation started")
651             }
652 
653             val window = findRootTaskIfPossible(apps)
654             if (window == null) {
655                 Log.i(TAG, "Aborting the animation as no window is opening")
656                 removeTimeout()
657                 iCallback?.invoke()
658 
659                 if (DEBUG_LAUNCH_ANIMATION) {
660                     Log.d(
661                         TAG,
662                         "Calling controller.onLaunchAnimationCancelled() [no window opening]"
663                     )
664                 }
665                 controller.onLaunchAnimationCancelled()
666                 listener?.onLaunchAnimationCancelled()
667                 return
668             }
669 
670             val navigationBar =
671                 nonApps?.firstOrNull {
672                     it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
673                 }
674 
675             val windowBounds = window.screenSpaceBounds
676             val endState =
677                 LaunchAnimator.State(
678                     top = windowBounds.top,
679                     bottom = windowBounds.bottom,
680                     left = windowBounds.left,
681                     right = windowBounds.right
682                 )
683             val windowBackgroundColor =
684                 window.taskInfo?.let { callback.getBackgroundColor(it) } ?: window.backgroundColor
685 
686             // TODO(b/184121838): We should somehow get the top and bottom radius of the window
687             // instead of recomputing isExpandingFullyAbove here.
688             val isExpandingFullyAbove =
689                 launchAnimator.isExpandingFullyAbove(controller.launchContainer, endState)
690             val endRadius =
691                 if (isExpandingFullyAbove) {
692                     // Most of the time, expanding fully above the root view means expanding in full
693                     // screen.
694                     ScreenDecorationsUtils.getWindowCornerRadius(context)
695                 } else {
696                     // This usually means we are in split screen mode, so 2 out of 4 corners will
697                     // have
698                     // a radius of 0.
699                     0f
700                 }
701             endState.topCornerRadius = endRadius
702             endState.bottomCornerRadius = endRadius
703 
704             // We animate the opening window and delegate the view expansion to [this.controller].
705             val delegate = this.controller
706             val controller =
707                 object : Controller by delegate {
708                     override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
709                         listener?.onLaunchAnimationStart()
710 
711                         if (DEBUG_LAUNCH_ANIMATION) {
712                             Log.d(
713                                 TAG,
714                                 "Calling controller.onLaunchAnimationStart(isExpandingFullyAbove=" +
715                                     "$isExpandingFullyAbove) [controller=$delegate]"
716                             )
717                         }
718                         delegate.onLaunchAnimationStart(isExpandingFullyAbove)
719                     }
720 
721                     override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
722                         listener?.onLaunchAnimationEnd()
723                         iCallback?.invoke()
724 
725                         if (DEBUG_LAUNCH_ANIMATION) {
726                             Log.d(
727                                 TAG,
728                                 "Calling controller.onLaunchAnimationEnd(isExpandingFullyAbove=" +
729                                     "$isExpandingFullyAbove) [controller=$delegate]"
730                             )
731                         }
732                         delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
733                     }
734 
735                     override fun onLaunchAnimationProgress(
736                         state: LaunchAnimator.State,
737                         progress: Float,
738                         linearProgress: Float
739                     ) {
740                         // Apply the state to the window only if it is visible, i.e. when the
741                         // expanding view is *not* visible.
742                         if (!state.visible) {
743                             applyStateToWindow(window, state, linearProgress)
744                         }
745                         navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
746 
747                         listener?.onLaunchAnimationProgress(linearProgress)
748                         delegate.onLaunchAnimationProgress(state, progress, linearProgress)
749                     }
750                 }
751 
752             animation =
753                 launchAnimator.startAnimation(
754                     controller,
755                     endState,
756                     windowBackgroundColor,
757                     fadeOutWindowBackgroundLayer = !controller.isBelowAnimatingWindow,
758                     drawHole = !controller.isBelowAnimatingWindow,
759                 )
760         }
761 
762         private fun applyStateToWindow(
763             window: RemoteAnimationTarget,
764             state: LaunchAnimator.State,
765             linearProgress: Float,
766         ) {
767             if (transactionApplierView.viewRootImpl == null || !window.leash.isValid) {
768                 // Don't apply any transaction if the view root we synchronize with was detached or
769                 // if the SurfaceControl associated with [window] is not valid, as
770                 // [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw.
771                 return
772             }
773 
774             val screenBounds = window.screenSpaceBounds
775             val centerX = (screenBounds.left + screenBounds.right) / 2f
776             val centerY = (screenBounds.top + screenBounds.bottom) / 2f
777             val width = screenBounds.right - screenBounds.left
778             val height = screenBounds.bottom - screenBounds.top
779 
780             // Scale the window. We use the max of (widthRatio, heightRatio) so that there is no
781             // blank space on any side.
782             val widthRatio = state.width.toFloat() / width
783             val heightRatio = state.height.toFloat() / height
784             val scale = maxOf(widthRatio, heightRatio)
785             matrix.reset()
786             matrix.setScale(scale, scale, centerX, centerY)
787 
788             // Align it to the top and center it in the x-axis.
789             val heightChange = height * scale - height
790             val translationX = state.centerX - centerX
791             val translationY = state.top - screenBounds.top + heightChange / 2f
792             matrix.postTranslate(translationX, translationY)
793 
794             // Crop it. The matrix will also be applied to the crop, so we apply the inverse
795             // operation. Given that we only scale (by factor > 0) then translate, we can assume
796             // that the matrix is invertible.
797             val cropX = state.left.toFloat() - screenBounds.left
798             val cropY = state.top.toFloat() - screenBounds.top
799             windowCropF.set(cropX, cropY, cropX + state.width, cropY + state.height)
800             matrix.invert(invertMatrix)
801             invertMatrix.mapRect(windowCropF)
802             windowCrop.set(
803                 windowCropF.left.roundToInt(),
804                 windowCropF.top.roundToInt(),
805                 windowCropF.right.roundToInt(),
806                 windowCropF.bottom.roundToInt()
807             )
808 
809             // The alpha of the opening window. If it opens above the expandable, then it should
810             // fade in progressively. Otherwise, it should be fully opaque and will be progressively
811             // revealed as the window background color layer above the window fades out.
812             val alpha =
813                 if (controller.isBelowAnimatingWindow) {
814                     val windowProgress =
815                         LaunchAnimator.getProgress(
816                             TIMINGS,
817                             linearProgress,
818                             TIMINGS.contentAfterFadeInDelay,
819                             TIMINGS.contentAfterFadeInDuration
820                         )
821 
822                     INTERPOLATORS.contentAfterFadeInInterpolator.getInterpolation(windowProgress)
823                 } else {
824                     1f
825                 }
826 
827             // The scale will also be applied to the corner radius, so we divide by the scale to
828             // keep the original radius. We use the max of (topCornerRadius, bottomCornerRadius) to
829             // make sure that the window does not draw itself behind the expanding view. This is
830             // especially important for lock screen animations, where the window is not clipped by
831             // the shade.
832             val cornerRadius = maxOf(state.topCornerRadius, state.bottomCornerRadius) / scale
833             val params =
834                 SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(window.leash)
835                     .withAlpha(alpha)
836                     .withMatrix(matrix)
837                     .withWindowCrop(windowCrop)
838                     .withCornerRadius(cornerRadius)
839                     .withVisibility(true)
840                     .build()
841 
842             transactionApplier.scheduleApply(params)
843         }
844 
845         private fun applyStateToNavigationBar(
846             navigationBar: RemoteAnimationTarget,
847             state: LaunchAnimator.State,
848             linearProgress: Float
849         ) {
850             if (transactionApplierView.viewRootImpl == null || !navigationBar.leash.isValid) {
851                 // Don't apply any transaction if the view root we synchronize with was detached or
852                 // if the SurfaceControl associated with [navigationBar] is not valid, as
853                 // [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw.
854                 return
855             }
856 
857             val fadeInProgress =
858                 LaunchAnimator.getProgress(
859                     TIMINGS,
860                     linearProgress,
861                     ANIMATION_DELAY_NAV_FADE_IN,
862                     ANIMATION_DURATION_NAV_FADE_OUT
863                 )
864 
865             val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(navigationBar.leash)
866             if (fadeInProgress > 0) {
867                 matrix.reset()
868                 matrix.setTranslate(
869                     0f,
870                     (state.top - navigationBar.sourceContainerBounds.top).toFloat()
871                 )
872                 windowCrop.set(state.left, 0, state.right, state.height)
873                 params
874                     .withAlpha(NAV_FADE_IN_INTERPOLATOR.getInterpolation(fadeInProgress))
875                     .withMatrix(matrix)
876                     .withWindowCrop(windowCrop)
877                     .withVisibility(true)
878             } else {
879                 val fadeOutProgress =
880                     LaunchAnimator.getProgress(
881                         TIMINGS,
882                         linearProgress,
883                         0,
884                         ANIMATION_DURATION_NAV_FADE_OUT
885                     )
886                 params.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress))
887             }
888 
889             transactionApplier.scheduleApply(params.build())
890         }
891 
892         private fun onAnimationTimedOut() {
893             if (cancelled) {
894                 return
895             }
896 
897             Log.i(TAG, "Remote animation timed out")
898             timedOut = true
899 
900             if (DEBUG_LAUNCH_ANIMATION) {
901                 Log.d(TAG, "Calling controller.onLaunchAnimationCancelled() [animation timed out]")
902             }
903             controller.onLaunchAnimationCancelled()
904             listener?.onLaunchAnimationCancelled()
905         }
906 
907         @UiThread
908         override fun onAnimationCancelled() {
909             if (timedOut) {
910                 return
911             }
912 
913             Log.i(TAG, "Remote animation was cancelled")
914             cancelled = true
915             removeTimeout()
916 
917             animation?.cancel()
918 
919             if (DEBUG_LAUNCH_ANIMATION) {
920                 Log.d(
921                     TAG,
922                     "Calling controller.onLaunchAnimationCancelled() [remote animation cancelled]",
923                 )
924             }
925             controller.onLaunchAnimationCancelled()
926             listener?.onLaunchAnimationCancelled()
927         }
928 
929         private fun IRemoteAnimationFinishedCallback.invoke() {
930             try {
931                 onAnimationFinished()
932             } catch (e: RemoteException) {
933                 e.printStackTrace()
934             }
935         }
936     }
937 }
938