1 /*
<lambda>null2  * 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.ActivityOptions
21 import android.app.ActivityTaskManager
22 import android.app.PendingIntent
23 import android.app.TaskInfo
24 import android.app.WindowConfiguration
25 import android.content.ComponentName
26 import android.graphics.Color
27 import android.graphics.Matrix
28 import android.graphics.PointF
29 import android.graphics.Rect
30 import android.graphics.RectF
31 import android.os.Binder
32 import android.os.Build
33 import android.os.Handler
34 import android.os.IBinder
35 import android.os.Looper
36 import android.os.RemoteException
37 import android.util.ArrayMap
38 import android.util.Log
39 import android.view.IRemoteAnimationFinishedCallback
40 import android.view.IRemoteAnimationRunner
41 import android.view.RemoteAnimationAdapter
42 import android.view.RemoteAnimationTarget
43 import android.view.SurfaceControl
44 import android.view.SyncRtSurfaceTransactionApplier
45 import android.view.View
46 import android.view.ViewGroup
47 import android.view.WindowManager
48 import android.view.WindowManager.TRANSIT_CLOSE
49 import android.view.WindowManager.TRANSIT_OPEN
50 import android.view.WindowManager.TRANSIT_TO_BACK
51 import android.view.WindowManager.TRANSIT_TO_FRONT
52 import android.view.animation.PathInterpolator
53 import android.window.IRemoteTransition
54 import android.window.IRemoteTransitionFinishedCallback
55 import android.window.RemoteTransition
56 import android.window.TransitionFilter
57 import android.window.TransitionInfo
58 import android.window.WindowAnimationState
59 import androidx.annotation.AnyThread
60 import androidx.annotation.BinderThread
61 import androidx.annotation.UiThread
62 import com.android.app.animation.Interpolators
63 import com.android.internal.annotations.VisibleForTesting
64 import com.android.internal.policy.ScreenDecorationsUtils
65 import com.android.systemui.Flags.activityTransitionUseLargestWindow
66 import com.android.systemui.Flags.translucentOccludingActivityFix
67 import com.android.systemui.animation.TransitionAnimator.Companion.assertLongLivedReturnAnimations
68 import com.android.systemui.animation.TransitionAnimator.Companion.assertReturnAnimations
69 import com.android.systemui.animation.TransitionAnimator.Companion.longLivedReturnAnimationsEnabled
70 import com.android.systemui.animation.TransitionAnimator.Companion.returnAnimationsEnabled
71 import com.android.systemui.animation.TransitionAnimator.Companion.toTransitionState
72 import com.android.wm.shell.shared.IShellTransitions
73 import com.android.wm.shell.shared.ShellTransitions
74 import com.android.wm.shell.shared.TransitionUtil
75 import java.util.concurrent.Executor
76 import kotlin.math.roundToInt
77 
78 private const val TAG = "ActivityTransitionAnimator"
79 
80 /**
81  * A class that allows activities to be started in a seamless way from a view that is transforming
82  * nicely into the starting window.
83  */
84 class ActivityTransitionAnimator
85 @JvmOverloads
86 constructor(
87     /** The executor that runs on the main thread. */
88     private val mainExecutor: Executor,
89 
90     /** The object used to register ephemeral returns and long-lived transitions. */
91     private val transitionRegister: TransitionRegister? = null,
92 
93     /** The animator used when animating a View into an app. */
94     private val transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor),
95 
96     /** The animator used when animating a Dialog into an app. */
97     // TODO(b/218989950): Remove this animator and instead set the duration of the dim fade out to
98     // TIMINGS.contentBeforeFadeOutDuration.
99     private val dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor),
100 
101     /**
102      * Whether we should disable the WindowManager timeout. This should be set to true in tests
103      * only.
104      */
105     // TODO(b/301385865): Remove this flag.
106     private val disableWmTimeout: Boolean = false,
107 ) {
108     @JvmOverloads
109     constructor(
110         mainExecutor: Executor,
111         shellTransitions: ShellTransitions,
112         transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor),
113         dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor),
114         disableWmTimeout: Boolean = false,
115     ) : this(
116         mainExecutor,
117         TransitionRegister.fromShellTransitions(shellTransitions),
118         transitionAnimator,
119         dialogToAppAnimator,
120         disableWmTimeout,
121     )
122 
123     @JvmOverloads
124     constructor(
125         mainExecutor: Executor,
126         iShellTransitions: IShellTransitions,
127         transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor),
128         dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor),
129         disableWmTimeout: Boolean = false,
130     ) : this(
131         mainExecutor,
132         TransitionRegister.fromIShellTransitions(iShellTransitions),
133         transitionAnimator,
134         dialogToAppAnimator,
135         disableWmTimeout,
136     )
137 
138     companion object {
139         /** The timings when animating a View into an app. */
140         @JvmField
141         val TIMINGS =
142             TransitionAnimator.Timings(
143                 totalDuration = 500L,
144                 contentBeforeFadeOutDelay = 0L,
145                 contentBeforeFadeOutDuration = 150L,
146                 contentAfterFadeInDelay = 150L,
147                 contentAfterFadeInDuration = 183L,
148             )
149 
150         /**
151          * The timings when animating a View into an app using a spring animator. These timings
152          * represent fractions of the progress between the spring's initial value and its final
153          * value.
154          */
155         val SPRING_TIMINGS =
156             TransitionAnimator.SpringTimings(
157                 contentBeforeFadeOutDelay = 0f,
158                 contentBeforeFadeOutDuration = 0.8f,
159                 contentAfterFadeInDelay = 0.85f,
160                 contentAfterFadeInDuration = 0.135f,
161             )
162 
163         /**
164          * The timings when animating a Dialog into an app. We need to wait at least 200ms before
165          * showing the app (which is under the dialog window) so that the dialog window dim is fully
166          * faded out, to avoid flicker.
167          */
168         val DIALOG_TIMINGS =
169             TIMINGS.copy(contentBeforeFadeOutDuration = 200L, contentAfterFadeInDelay = 200L)
170 
171         /** The interpolators when animating a View or a dialog into an app. */
172         val INTERPOLATORS =
173             TransitionAnimator.Interpolators(
174                 positionInterpolator = Interpolators.EMPHASIZED,
175                 positionXInterpolator = Interpolators.EMPHASIZED_COMPLEMENT,
176                 contentBeforeFadeOutInterpolator = Interpolators.LINEAR_OUT_SLOW_IN,
177                 contentAfterFadeInInterpolator = PathInterpolator(0f, 0f, 0.6f, 1f),
178             )
179 
180         /** The interpolators when animating a View into an app using a spring animator. */
181         val SPRING_INTERPOLATORS =
182             INTERPOLATORS.copy(
183                 contentBeforeFadeOutInterpolator = Interpolators.DECELERATE_1_5,
184                 contentAfterFadeInInterpolator = Interpolators.SLOW_OUT_LINEAR_IN,
185             )
186 
187         // TODO(b/288507023): Remove this flag.
188         @JvmField val DEBUG_TRANSITION_ANIMATION = Build.IS_DEBUGGABLE
189 
190         /** Durations & interpolators for the navigation bar fading in & out. */
191         private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
192         private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L
193         private val ANIMATION_DELAY_NAV_FADE_IN =
194             TIMINGS.totalDuration - ANIMATION_DURATION_NAV_FADE_IN
195 
196         private val NAV_FADE_IN_INTERPOLATOR = Interpolators.STANDARD_DECELERATE
197         private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f)
198 
199         /** The time we wait before timing out the remote animation after starting the intent. */
200         private const val TRANSITION_TIMEOUT = 1_000L
201 
202         /**
203          * The time we wait before we Log.wtf because the remote animation was neither started or
204          * cancelled by WM.
205          */
206         private const val LONG_TRANSITION_TIMEOUT = 5_000L
207 
208         private fun defaultTransitionAnimator(mainExecutor: Executor): TransitionAnimator {
209             return TransitionAnimator(
210                 mainExecutor,
211                 TIMINGS,
212                 INTERPOLATORS,
213                 SPRING_TIMINGS,
214                 SPRING_INTERPOLATORS,
215             )
216         }
217 
218         private fun defaultDialogToAppAnimator(mainExecutor: Executor): TransitionAnimator {
219             return TransitionAnimator(mainExecutor, DIALOG_TIMINGS, INTERPOLATORS)
220         }
221     }
222 
223     /**
224      * The callback of this animator. This should be set before any call to
225      * [start(Pending)IntentWithAnimation].
226      */
227     var callback: Callback? = null
228 
229     /** The set of [Listener] that should be notified of any animation started by this animator. */
230     private val listeners = LinkedHashSet<Listener>()
231 
232     /** Top-level listener that can be used to notify all registered [listeners]. */
233     private val lifecycleListener =
234         object : Listener {
235             override fun onTransitionAnimationStart() {
236                 listeners.forEach { it.onTransitionAnimationStart() }
237             }
238 
239             override fun onTransitionAnimationEnd() {
240                 listeners.forEach { it.onTransitionAnimationEnd() }
241             }
242 
243             override fun onTransitionAnimationProgress(linearProgress: Float) {
244                 listeners.forEach { it.onTransitionAnimationProgress(linearProgress) }
245             }
246 
247             override fun onTransitionAnimationCancelled() {
248                 listeners.forEach { it.onTransitionAnimationCancelled() }
249             }
250         }
251 
252     /** Book-keeping for long-lived transitions that are currently registered. */
253     private val longLivedTransitions =
254         HashMap<TransitionCookie, Pair<RemoteTransition, RemoteTransition>>()
255 
256     /**
257      * Start an intent and animate the opening window. The intent will be started by running
258      * [intentStarter], which should use the provided [RemoteAnimationAdapter] and return the launch
259      * result. [controller] is responsible from animating the view from which the intent was started
260      * in [Controller.onTransitionAnimationProgress]. No animation will start if there is no window
261      * opening.
262      *
263      * If [controller] is null or [animate] is false, then the intent will be started and no
264      * animation will run.
265      *
266      * If possible, you should pass the [packageName] of the intent that will be started so that
267      * trampoline activity launches will also be animated.
268      *
269      * If the device is currently locked, the user will have to unlock it before the intent is
270      * started unless [showOverLockscreen] is true. In that case, the activity will be started
271      * directly over the lockscreen.
272      *
273      * This method will throw any exception thrown by [intentStarter].
274      */
275     @JvmOverloads
276     fun startIntentWithAnimation(
277         controller: Controller?,
278         animate: Boolean = true,
279         packageName: String? = null,
280         showOverLockscreen: Boolean = false,
281         intentStarter: (RemoteAnimationAdapter?) -> Int,
282     ) {
283         if (controller == null || !animate) {
284             Log.i(TAG, "Starting intent with no animation")
285             intentStarter(null)
286             controller?.callOnIntentStartedOnMainThread(willAnimate = false)
287             return
288         }
289 
290         val callback =
291             this.callback
292                 ?: throw IllegalStateException(
293                     "ActivityTransitionAnimator.callback must be set before using this animator"
294                 )
295         val runner = createRunner(controller)
296         val runnerDelegate = runner.delegate
297         val hideKeyguardWithAnimation = callback.isOnKeyguard() && !showOverLockscreen
298 
299         // Pass the RemoteAnimationAdapter to the intent starter only if we are not hiding the
300         // keyguard with the animation
301         val animationAdapter =
302             if (!hideKeyguardWithAnimation) {
303                 RemoteAnimationAdapter(
304                     runner,
305                     TIMINGS.totalDuration,
306                     TIMINGS.totalDuration - 150, /* statusBarTransitionDelay */
307                 )
308             } else {
309                 null
310             }
311 
312         // Register the remote animation for the given package to also animate trampoline
313         // activity launches.
314         if (packageName != null && animationAdapter != null) {
315             try {
316                 ActivityTaskManager.getService()
317                     .registerRemoteAnimationForNextActivityStart(
318                         packageName,
319                         animationAdapter,
320                         null, /* launchCookie */
321                     )
322             } catch (e: RemoteException) {
323                 Log.w(TAG, "Unable to register the remote animation", e)
324             }
325         }
326 
327         if (animationAdapter != null && controller.transitionCookie != null) {
328             registerEphemeralReturnAnimation(controller, transitionRegister)
329         }
330 
331         val launchResult = intentStarter(animationAdapter)
332 
333         // Only animate if the app is not already on top and will be opened, unless we are on the
334         // keyguard.
335         val willAnimate =
336             launchResult == ActivityManager.START_TASK_TO_FRONT ||
337                 launchResult == ActivityManager.START_SUCCESS ||
338                 (launchResult == ActivityManager.START_DELIVERED_TO_TOP &&
339                     hideKeyguardWithAnimation)
340 
341         Log.i(
342             TAG,
343             "launchResult=$launchResult willAnimate=$willAnimate " +
344                 "hideKeyguardWithAnimation=$hideKeyguardWithAnimation",
345         )
346         controller.callOnIntentStartedOnMainThread(willAnimate)
347 
348         // If we expect an animation, post a timeout to cancel it in case the remote animation is
349         // never started.
350         if (willAnimate) {
351             if (longLivedReturnAnimationsEnabled()) {
352                 runner.postTimeouts()
353             } else {
354                 runnerDelegate!!.postTimeouts()
355             }
356 
357             // Hide the keyguard using the launch animation instead of the default unlock animation.
358             if (hideKeyguardWithAnimation) {
359                 callback.hideKeyguardWithAnimation(runner)
360             }
361         } else {
362             // We need to make sure delegate references are dropped to avoid memory leaks.
363             runner.dispose()
364         }
365     }
366 
367     private fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
368         if (Looper.myLooper() != Looper.getMainLooper()) {
369             mainExecutor.execute { callOnIntentStartedOnMainThread(willAnimate) }
370         } else {
371             if (DEBUG_TRANSITION_ANIMATION) {
372                 Log.d(
373                     TAG,
374                     "Calling controller.onIntentStarted(willAnimate=$willAnimate) " +
375                         "[controller=$this]",
376                 )
377             }
378             this.onIntentStarted(willAnimate)
379         }
380     }
381 
382     /**
383      * Same as [startIntentWithAnimation] but allows [intentStarter] to throw a
384      * [PendingIntent.CanceledException] which must then be handled by the caller. This is useful
385      * for Java caller starting a [PendingIntent].
386      *
387      * If possible, you should pass the [packageName] of the intent that will be started so that
388      * trampoline activity launches will also be animated.
389      */
390     @Throws(PendingIntent.CanceledException::class)
391     @JvmOverloads
392     fun startPendingIntentWithAnimation(
393         controller: Controller?,
394         animate: Boolean = true,
395         packageName: String? = null,
396         showOverLockscreen: Boolean = false,
397         intentStarter: PendingIntentStarter,
398     ) {
399         startIntentWithAnimation(controller, animate, packageName, showOverLockscreen) {
400             intentStarter.startPendingIntent(it)
401         }
402     }
403 
404     /**
405      * Uses [transitionRegister] to set up the return animation for the given [launchController].
406      *
407      * De-registration is set up automatically once the return animation is run.
408      *
409      * TODO(b/339194555): automatically de-register when the launchable is detached.
410      */
411     private fun registerEphemeralReturnAnimation(
412         launchController: Controller,
413         transitionRegister: TransitionRegister?,
414     ) {
415         if (!returnAnimationsEnabled()) return
416 
417         var cleanUpRunnable: Runnable? = null
418         val returnRunner =
419             createRunner(
420                 object : DelegateTransitionAnimatorController(launchController) {
421                     override val isLaunching = false
422 
423                     override fun onTransitionAnimationCancelled(
424                         newKeyguardOccludedState: Boolean?
425                     ) {
426                         super.onTransitionAnimationCancelled(newKeyguardOccludedState)
427                         onDispose()
428                     }
429 
430                     override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
431                         super.onTransitionAnimationEnd(isExpandingFullyAbove)
432                         onDispose()
433                     }
434 
435                     override fun onDispose() {
436                         super.onDispose()
437                         cleanUpRunnable?.run()
438                     }
439                 }
440             )
441 
442         // mTypeSet and mModes match back signals only, and not home. This is on purpose, because
443         // we only want ephemeral return animations triggered in these scenarios.
444         val filter =
445             TransitionFilter().apply {
446                 mTypeSet = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK)
447                 mRequirements =
448                     arrayOf(
449                         TransitionFilter.Requirement().apply {
450                             mLaunchCookie = launchController.transitionCookie
451                             mModes = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK)
452                         }
453                     )
454             }
455         val transition =
456             RemoteTransition(
457                 RemoteAnimationRunnerCompat.wrap(returnRunner),
458                 "${launchController.transitionCookie}_returnTransition",
459             )
460 
461         transitionRegister?.register(
462             filter,
463             transition,
464             includeTakeover = longLivedReturnAnimationsEnabled(),
465         )
466         cleanUpRunnable = Runnable { transitionRegister?.unregister(transition) }
467     }
468 
469     /** Add a [Listener] that can listen to transition animations. */
470     fun addListener(listener: Listener) {
471         listeners.add(listener)
472     }
473 
474     /** Remove a [Listener]. */
475     fun removeListener(listener: Listener) {
476         listeners.remove(listener)
477     }
478 
479     /** Create a new animation [Runner] controlled by [controller]. */
480     @VisibleForTesting
481     @JvmOverloads
482     fun createRunner(controller: Controller, longLived: Boolean = false): Runner {
483         if (longLived) assertLongLivedReturnAnimations()
484 
485         // Make sure we use the modified timings when animating a dialog into an app.
486         val transitionAnimator =
487             if (controller.isDialogLaunch) {
488                 dialogToAppAnimator
489             } else {
490                 transitionAnimator
491             }
492 
493         return Runner(controller, callback!!, transitionAnimator, lifecycleListener, longLived)
494     }
495 
496     interface PendingIntentStarter {
497         /**
498          * Start a pending intent using the provided [animationAdapter] and return the launch
499          * result.
500          */
501         @Throws(PendingIntent.CanceledException::class)
502         fun startPendingIntent(animationAdapter: RemoteAnimationAdapter?): Int
503     }
504 
505     interface Callback {
506         /** Whether we are currently on the keyguard or not. */
507         fun isOnKeyguard(): Boolean = false
508 
509         /** Hide the keyguard and animate using [runner]. */
510         fun hideKeyguardWithAnimation(runner: IRemoteAnimationRunner) {
511             throw UnsupportedOperationException()
512         }
513 
514         /* Get the background color of [task]. */
515         fun getBackgroundColor(task: TaskInfo): Int
516     }
517 
518     interface Listener {
519         /** Called when an activity transition animation started. */
520         fun onTransitionAnimationStart() {}
521 
522         /**
523          * Called when an activity transition animation is finished. This will be called if and only
524          * if [onTransitionAnimationStart] was called earlier.
525          */
526         fun onTransitionAnimationEnd() {}
527 
528         /**
529          * The animation was cancelled. Note that [onTransitionAnimationEnd] will still be called
530          * after this if the animation was already started, i.e. if [onTransitionAnimationStart] was
531          * called before the cancellation.
532          */
533         fun onTransitionAnimationCancelled() {}
534 
535         /** Called when an activity transition animation made progress. */
536         fun onTransitionAnimationProgress(linearProgress: Float) {}
537     }
538 
539     /**
540      * A controller that takes care of applying the animation to an expanding view.
541      *
542      * Note that all callbacks (onXXX methods) are all called on the main thread.
543      */
544     interface Controller : TransitionAnimator.Controller {
545         companion object {
546             /**
547              * Return a [Controller] that will animate and expand [view] into the opening window.
548              *
549              * Important: The view must be attached to a [ViewGroup] when calling this function and
550              * during the animation. For safety, this method will return null when it is not. The
551              * view must also implement [LaunchableView], otherwise this method will throw.
552              *
553              * Note: The background of [view] should be a (rounded) rectangle so that it can be
554              * properly animated.
555              */
556             @JvmOverloads
557             @JvmStatic
558             fun fromView(
559                 view: View,
560                 cujType: Int? = null,
561                 cookie: TransitionCookie? = null,
562                 component: ComponentName? = null,
563                 returnCujType: Int? = null,
564                 isEphemeral: Boolean = true,
565             ): Controller? {
566                 // Make sure the View we launch from implements LaunchableView to avoid visibility
567                 // issues.
568                 if (view !is LaunchableView) {
569                     throw IllegalArgumentException(
570                         "An ActivityTransitionAnimator.Controller was created from a View that " +
571                             "does not implement LaunchableView. This can lead to subtle bugs " +
572                             "where the visibility of the View we are launching from is not what " +
573                             "we expected."
574                     )
575                 }
576 
577                 if (view.parent !is ViewGroup) {
578                     Log.e(
579                         TAG,
580                         "Skipping animation as view $view is not attached to a ViewGroup",
581                         Exception(),
582                     )
583                     return null
584                 }
585 
586                 return GhostedViewTransitionAnimatorController(
587                     view,
588                     cujType,
589                     cookie,
590                     component,
591                     returnCujType,
592                     isEphemeral,
593                 )
594             }
595         }
596 
597         /**
598          * Whether this controller is controlling a dialog launch. This will be used to adapt the
599          * timings, making sure we don't show the app until the dialog dim had the time to fade out.
600          */
601         // TODO(b/218989950): Remove this.
602         val isDialogLaunch: Boolean
603             get() = false
604 
605         /**
606          * Whether the expandable controller by this [Controller] is below the window that is going
607          * to be animated.
608          *
609          * This should be `false` when animating an app from or to the shade or status bar, given
610          * that they are drawn above all apps. This is usually `true` when using this animator in a
611          * normal app or a launcher, that are drawn below the animating activity/window.
612          */
613         val isBelowAnimatingWindow: Boolean
614             get() = false
615 
616         /**
617          * The cookie associated with the transition controlled by this [Controller].
618          *
619          * This should be defined for all return [Controller] (when [isLaunching] is false) and for
620          * their associated launch [Controller]s.
621          *
622          * For the recommended format, see [TransitionCookie].
623          */
624         val transitionCookie: TransitionCookie?
625             get() = null
626 
627         /**
628          * The [ComponentName] of the activity whose window is tied to this [Controller].
629          *
630          * This is used as a fallback when a cookie is defined but there is no match (e.g. when a
631          * matching activity was launched by a mean different from the launchable in this
632          * [Controller]), and should be defined for all long-lived registered [Controller]s.
633          */
634         val component: ComponentName?
635             get() = null
636 
637         /**
638          * The intent was started. If [willAnimate] is false, nothing else will happen and the
639          * animation will not be started.
640          */
641         fun onIntentStarted(willAnimate: Boolean) {}
642 
643         /**
644          * The animation was cancelled. Note that [onTransitionAnimationEnd] will still be called
645          * after this if the animation was already started, i.e. if [onTransitionAnimationStart] was
646          * called before the cancellation.
647          *
648          * If this transition animation affected the occlusion state of the keyguard, WM will
649          * provide us with [newKeyguardOccludedState] so that we can set the occluded state
650          * appropriately.
651          */
652         fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {}
653 
654         /** The controller will not be used again. Clean up the relevant internal state. */
655         fun onDispose() {}
656     }
657 
658     /**
659      * Registers [controller] as a long-lived transition handler for launch and return animations.
660      *
661      * The [controller] will only be used for transitions matching the [TransitionCookie] defined
662      * within it, or the [ComponentName] if the cookie matching fails. Both fields are mandatory for
663      * this registration.
664      */
665     fun register(controller: Controller) {
666         assertLongLivedReturnAnimations()
667 
668         if (transitionRegister == null) {
669             throw IllegalStateException(
670                 "A RemoteTransitionRegister must be provided when creating this animator in " +
671                     "order to use long-lived animations"
672             )
673         }
674 
675         val cookie =
676             controller.transitionCookie
677                 ?: throw IllegalStateException(
678                     "A cookie must be defined in order to use long-lived animations"
679                 )
680         val component =
681             controller.component
682                 ?: throw IllegalStateException(
683                     "A component must be defined in order to use long-lived animations"
684                 )
685 
686         // Make sure that any previous registrations linked to the same cookie are gone.
687         unregister(cookie)
688 
689         val launchFilter =
690             TransitionFilter().apply {
691                 mRequirements =
692                     arrayOf(
693                         TransitionFilter.Requirement().apply {
694                             mActivityType = WindowConfiguration.ACTIVITY_TYPE_STANDARD
695                             mModes = intArrayOf(TRANSIT_OPEN, TRANSIT_TO_FRONT)
696                             mTopActivity = component
697                         }
698                     )
699             }
700         val launchRemoteTransition =
701             RemoteTransition(
702                 OriginTransition(createRunner(controller, longLived = true)),
703                 "${cookie}_launchTransition",
704             )
705         transitionRegister.register(launchFilter, launchRemoteTransition, includeTakeover = true)
706 
707         val returnController =
708             object : Controller by controller {
709                 override val isLaunching: Boolean = false
710             }
711         // Cross-task close transitions should not use this animation, so we only register it for
712         // when the opening window is Launcher.
713         val returnFilter =
714             TransitionFilter().apply {
715                 mRequirements =
716                     arrayOf(
717                         TransitionFilter.Requirement().apply {
718                             mActivityType = WindowConfiguration.ACTIVITY_TYPE_STANDARD
719                             mModes = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK)
720                             mTopActivity = component
721                         },
722                         TransitionFilter.Requirement().apply {
723                             mActivityType = WindowConfiguration.ACTIVITY_TYPE_HOME
724                             mModes = intArrayOf(TRANSIT_OPEN, TRANSIT_TO_FRONT)
725                         },
726                     )
727             }
728         val returnRemoteTransition =
729             RemoteTransition(
730                 OriginTransition(createRunner(returnController, longLived = true)),
731                 "${cookie}_returnTransition",
732             )
733         transitionRegister.register(returnFilter, returnRemoteTransition, includeTakeover = true)
734 
735         longLivedTransitions[cookie] = Pair(launchRemoteTransition, returnRemoteTransition)
736     }
737 
738     /** Unregisters all controllers previously registered that contain [cookie]. */
739     fun unregister(cookie: TransitionCookie) {
740         val transitions = longLivedTransitions[cookie] ?: return
741         transitionRegister?.unregister(transitions.first)
742         transitionRegister?.unregister(transitions.second)
743         longLivedTransitions.remove(cookie)
744     }
745 
746     /**
747      * Invokes [onAnimationComplete] when animation is either cancelled or completed. Delegates all
748      * events to the passed [delegate].
749      */
750     @VisibleForTesting
751     inner class DelegatingAnimationCompletionListener(
752         private val delegate: Listener?,
753         private val onAnimationComplete: () -> Unit,
754     ) : Listener {
755         var cancelled = false
756 
757         override fun onTransitionAnimationStart() {
758             delegate?.onTransitionAnimationStart()
759         }
760 
761         override fun onTransitionAnimationProgress(linearProgress: Float) {
762             delegate?.onTransitionAnimationProgress(linearProgress)
763         }
764 
765         override fun onTransitionAnimationEnd() {
766             delegate?.onTransitionAnimationEnd()
767             if (!cancelled) {
768                 onAnimationComplete.invoke()
769             }
770         }
771 
772         override fun onTransitionAnimationCancelled() {
773             cancelled = true
774             delegate?.onTransitionAnimationCancelled()
775             onAnimationComplete.invoke()
776         }
777     }
778 
779     /** [Runner] wrapper that supports animation takeovers. */
780     private inner class OriginTransition(private val runner: Runner) : IRemoteTransition {
781         private val delegate = RemoteAnimationRunnerCompat.wrap(runner)
782 
783         init {
784             assertLongLivedReturnAnimations()
785         }
786 
787         override fun startAnimation(
788             token: IBinder?,
789             info: TransitionInfo?,
790             t: SurfaceControl.Transaction?,
791             finishCallback: IRemoteTransitionFinishedCallback?,
792         ) {
793             delegate.startAnimation(token, info, t, finishCallback)
794         }
795 
796         override fun mergeAnimation(
797             transition: IBinder?,
798             info: TransitionInfo?,
799             t: SurfaceControl.Transaction?,
800             mergeTarget: IBinder?,
801             finishCallback: IRemoteTransitionFinishedCallback?,
802         ) {
803             delegate.mergeAnimation(transition, info, t, mergeTarget, finishCallback)
804         }
805 
806         override fun onTransitionConsumed(transition: IBinder?, aborted: Boolean) {
807             delegate.onTransitionConsumed(transition, aborted)
808         }
809 
810         override fun takeOverAnimation(
811             token: IBinder?,
812             info: TransitionInfo?,
813             t: SurfaceControl.Transaction?,
814             finishCallback: IRemoteTransitionFinishedCallback?,
815             states: Array<WindowAnimationState>,
816         ) {
817             if (info == null || t == null) {
818                 Log.e(
819                     TAG,
820                     "Skipping the animation takeover because the required data is missing: " +
821                         "info=$info, transaction=$t",
822                 )
823                 return
824             }
825 
826             // The following code converts the contents of the given TransitionInfo into
827             // RemoteAnimationTargets. This is necessary because we must currently support both the
828             // new (Shell, remote transitions) and old (remote animations) framework to maintain
829             // functionality for all users of the library.
830             val apps = ArrayList<RemoteAnimationTarget>()
831             val filteredStates = ArrayList<WindowAnimationState>()
832             val leashMap = ArrayMap<SurfaceControl, SurfaceControl>()
833             val leafTaskFilter = TransitionUtil.LeafTaskFilter()
834 
835             // About layering: we divide up the "layer space" into 2 regions (each the size of the
836             // change count). This lets us categorize things into above and below while
837             // maintaining their relative ordering.
838             val belowLayers = info.changes.size
839             val aboveLayers = info.changes.size * 2
840             for (i in info.changes.indices) {
841                 val change = info.changes[i]
842                 if (change == null || change.taskInfo == null) {
843                     continue
844                 }
845 
846                 val taskInfo = change.taskInfo
847 
848                 if (TransitionUtil.isWallpaper(change)) {
849                     val target =
850                         TransitionUtil.newTarget(
851                             change,
852                             belowLayers - i, // wallpapers go into the "below" layer space
853                             info,
854                             t,
855                             leashMap,
856                         )
857 
858                     // Make all the wallpapers opaque.
859                     t.setAlpha(target.leash, 1f)
860                 } else if (leafTaskFilter.test(change)) {
861                     // Start by putting everything into the "below" layer space.
862                     val target =
863                         TransitionUtil.newTarget(change, belowLayers - i, info, t, leashMap)
864                     apps.add(target)
865                     filteredStates.add(states[i])
866 
867                     // Make all the apps opaque.
868                     t.setAlpha(target.leash, 1f)
869 
870                     if (
871                         TransitionUtil.isClosingType(change.mode) &&
872                             taskInfo?.topActivityType != WindowConfiguration.ACTIVITY_TYPE_HOME
873                     ) {
874                         // Raise closing task to "above" layer so it isn't covered.
875                         t.setLayer(target.leash, aboveLayers - i)
876                     } else if (TransitionUtil.isOpeningType(change.mode)) {
877                         // Put into the "below" layer space.
878                         t.setLayer(target.leash, belowLayers - i)
879                     }
880                 } else if (TransitionInfo.isIndependent(change, info)) {
881                     // Root tasks
882                     if (TransitionUtil.isClosingType(change.mode)) {
883                         // Raise closing task to "above" layer so it isn't covered.
884                         t.setLayer(change.leash, aboveLayers - i)
885                     } else if (TransitionUtil.isOpeningType(change.mode)) {
886                         // Put into the "below" layer space.
887                         t.setLayer(change.leash, belowLayers - i)
888                     }
889                 } else if (TransitionUtil.isDividerBar(change)) {
890                     val target =
891                         TransitionUtil.newTarget(change, belowLayers - i, info, t, leashMap)
892                     apps.add(target)
893                     filteredStates.add(states[i])
894                 }
895             }
896 
897             val wrappedCallback: IRemoteAnimationFinishedCallback =
898                 object : IRemoteAnimationFinishedCallback.Stub() {
899                     override fun onAnimationFinished() {
900                         leashMap.clear()
901                         val finishTransaction = SurfaceControl.Transaction()
902                         finishCallback?.onTransitionFinished(null, finishTransaction)
903                         finishTransaction.close()
904                     }
905                 }
906 
907             runner.takeOverAnimation(
908                 apps.toTypedArray(),
909                 filteredStates.toTypedArray(),
910                 t,
911                 wrappedCallback,
912             )
913         }
914 
915         override fun asBinder(): IBinder {
916             return delegate.asBinder()
917         }
918     }
919 
920     @VisibleForTesting
921     inner class Runner(
922         /**
923          * This can hold a reference to a view, so it needs to be cleaned up and can't be held on to
924          * forever when ![longLived].
925          */
926         private var controller: Controller?,
927         private val callback: Callback,
928         /** The animator to use to animate the window transition. */
929         private val transitionAnimator: TransitionAnimator,
930         /** Listener for animation lifecycle events. */
931         private val listener: Listener? = null,
932         /**
933          * Whether the internal should be kept around after execution for later usage. IMPORTANT:
934          * should always be false if this [Runner] is to be used directly with [ActivityOptions]
935          * (i.e. for ephemeral launches), or the controller will leak its view.
936          */
937         private val longLived: Boolean = false,
938     ) : IRemoteAnimationRunner.Stub() {
939         // This is being passed across IPC boundaries and cycles (through PendingIntentRecords,
940         // etc.) are possible. So we need to make sure we drop any references that might
941         // transitively cause leaks when we're done with animation.
942         @VisibleForTesting var delegate: AnimationDelegate?
943 
944         init {
945             delegate = null
946             if (!longLived) {
947                 // Ephemeral launches bundle the runner with the launch request (instead of being
948                 // registered ahead of time for later use). This means that there could be a timeout
949                 // between creation and invocation, so the delegate needs to exist from the
950                 // beginning in order to handle such timeout.
951                 createDelegate()
952             }
953         }
954 
955         @BinderThread
956         override fun onAnimationStart(
957             transit: Int,
958             apps: Array<out RemoteAnimationTarget>?,
959             wallpapers: Array<out RemoteAnimationTarget>?,
960             nonApps: Array<out RemoteAnimationTarget>?,
961             finishedCallback: IRemoteAnimationFinishedCallback?,
962         ) {
963             initAndRun(finishedCallback) { delegate ->
964                 delegate.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback)
965             }
966         }
967 
968         @VisibleForTesting
969         @BinderThread
970         fun takeOverAnimation(
971             apps: Array<RemoteAnimationTarget>?,
972             windowAnimationStates: Array<WindowAnimationState>,
973             startTransaction: SurfaceControl.Transaction,
974             finishedCallback: IRemoteAnimationFinishedCallback?,
975         ) {
976             assertLongLivedReturnAnimations()
977             initAndRun(finishedCallback) { delegate ->
978                 delegate.takeOverAnimation(
979                     apps,
980                     windowAnimationStates,
981                     startTransaction,
982                     finishedCallback,
983                 )
984             }
985         }
986 
987         @BinderThread
988         private fun initAndRun(
989             finishedCallback: IRemoteAnimationFinishedCallback?,
990             performAnimation: (AnimationDelegate) -> Unit,
991         ) {
992             maybeSetUp()
993             val delegate = delegate
994             mainExecutor.execute {
995                 if (delegate == null) {
996                     Log.i(TAG, "onAnimationStart called after completion")
997                     // Animation started too late and timed out already. We need to still
998                     // signal back that we're done with it.
999                     finishedCallback?.onAnimationFinished()
1000                 } else {
1001                     performAnimation(delegate)
1002                 }
1003             }
1004         }
1005 
1006         @BinderThread
1007         override fun onAnimationCancelled() {
1008             val delegate = delegate
1009             mainExecutor.execute {
1010                 delegate ?: Log.wtf(TAG, "onAnimationCancelled called after completion")
1011                 delegate?.onAnimationCancelled()
1012             }
1013         }
1014 
1015         @VisibleForTesting
1016         @UiThread
1017         fun postTimeouts() {
1018             maybeSetUp()
1019             delegate?.postTimeouts()
1020         }
1021 
1022         @AnyThread
1023         private fun maybeSetUp() {
1024             if (!longLived || delegate != null) return
1025             createDelegate()
1026         }
1027 
1028         @AnyThread
1029         private fun createDelegate() {
1030             if (controller == null) return
1031             delegate =
1032                 AnimationDelegate(
1033                     mainExecutor,
1034                     controller!!,
1035                     callback,
1036                     DelegatingAnimationCompletionListener(listener, this::dispose),
1037                     transitionAnimator,
1038                     disableWmTimeout,
1039                 )
1040         }
1041 
1042         @AnyThread
1043         fun dispose() {
1044             // Drop references to animation controller once we're done with the animation
1045             // to avoid leaking.
1046             mainExecutor.execute {
1047                 delegate = null
1048                 // When long lived, the same Runner can be used more than once. In this case we need
1049                 // to keep the controller around so we can rebuild the delegate on demand.
1050                 if (!longLived) controller = null
1051             }
1052         }
1053     }
1054 
1055     class AnimationDelegate
1056     @JvmOverloads
1057     constructor(
1058         private val mainExecutor: Executor,
1059         private val controller: Controller,
1060         private val callback: Callback,
1061         /** Listener for animation lifecycle events. */
1062         private val listener: Listener? = null,
1063         /** The animator to use to animate the window transition. */
1064         private val transitionAnimator: TransitionAnimator =
1065             defaultTransitionAnimator(mainExecutor),
1066 
1067         /**
1068          * Whether we should disable the WindowManager timeout. This should be set to true in tests
1069          * only.
1070          */
1071         // TODO(b/301385865): Remove this flag.
1072         disableWmTimeout: Boolean = false,
1073     ) : RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> {
1074         private val transitionContainer = controller.transitionContainer
1075         private val context = transitionContainer.context
1076         private val transactionApplierView =
1077             controller.openingWindowSyncView ?: controller.transitionContainer
1078         private val transactionApplier = SyncRtSurfaceTransactionApplier(transactionApplierView)
1079         private val timeoutHandler =
1080             if (!disableWmTimeout) {
1081                 Handler(Looper.getMainLooper())
1082             } else {
1083                 null
1084             }
1085 
1086         private val matrix = Matrix()
1087         private val invertMatrix = Matrix()
1088         private var windowCrop = Rect()
1089         private var windowCropF = RectF()
1090         private var timedOut = false
1091         private var cancelled = false
1092         private var animation: TransitionAnimator.Animation? = null
1093 
1094         /**
1095          * A timeout to cancel the transition animation if the remote animation is not started or
1096          * cancelled within [TRANSITION_TIMEOUT] milliseconds after the intent was started.
1097          *
1098          * Note that this is important to keep this a Runnable (and not a Kotlin lambda), otherwise
1099          * it will be automatically converted when posted and we wouldn't be able to remove it after
1100          * posting it.
1101          */
1102         private var onTimeout = Runnable { onAnimationTimedOut() }
1103 
1104         /**
1105          * A long timeout to Log.wtf (signaling a bug in WM) when the remote animation wasn't
1106          * started or cancelled within [LONG_TRANSITION_TIMEOUT] milliseconds after the intent was
1107          * started.
1108          */
1109         private var onLongTimeout = Runnable {
1110             Log.wtf(
1111                 TAG,
1112                 "The remote animation was neither cancelled or started within " +
1113                     "$LONG_TRANSITION_TIMEOUT",
1114             )
1115         }
1116 
1117         init {
1118             // We do this check here to cover all entry points, including Launcher which doesn't
1119             // call startIntentWithAnimation()
1120             if (!controller.isLaunching) assertReturnAnimations()
1121         }
1122 
1123         @UiThread
1124         internal fun postTimeouts() {
1125             if (timeoutHandler != null) {
1126                 timeoutHandler.postDelayed(onTimeout, TRANSITION_TIMEOUT)
1127                 timeoutHandler.postDelayed(onLongTimeout, LONG_TRANSITION_TIMEOUT)
1128             }
1129         }
1130 
1131         private fun removeTimeouts() {
1132             if (timeoutHandler != null) {
1133                 timeoutHandler.removeCallbacks(onTimeout)
1134                 timeoutHandler.removeCallbacks(onLongTimeout)
1135             }
1136         }
1137 
1138         @UiThread
1139         override fun onAnimationStart(
1140             @WindowManager.TransitionOldType transit: Int,
1141             apps: Array<out RemoteAnimationTarget>?,
1142             wallpapers: Array<out RemoteAnimationTarget>?,
1143             nonApps: Array<out RemoteAnimationTarget>?,
1144             callback: IRemoteAnimationFinishedCallback?,
1145         ) {
1146             val window = setUpAnimation(apps, callback) ?: return
1147 
1148             if (controller.windowAnimatorState == null || !longLivedReturnAnimationsEnabled()) {
1149                 val navigationBar =
1150                     nonApps?.firstOrNull {
1151                         it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
1152                     }
1153 
1154                 startAnimation(window, navigationBar, iCallback = callback)
1155             } else {
1156                 // If a [controller.windowAnimatorState] exists, treat this like a takeover.
1157                 takeOverAnimationInternal(
1158                     window,
1159                     startWindowState = null,
1160                     startTransaction = null,
1161                     callback,
1162                 )
1163             }
1164         }
1165 
1166         @UiThread
1167         internal fun takeOverAnimation(
1168             apps: Array<out RemoteAnimationTarget>?,
1169             startWindowStates: Array<WindowAnimationState>,
1170             startTransaction: SurfaceControl.Transaction,
1171             callback: IRemoteAnimationFinishedCallback?,
1172         ) {
1173             val window = setUpAnimation(apps, callback) ?: return
1174             val startWindowState = startWindowStates[apps!!.indexOf(window)]
1175             takeOverAnimationInternal(window, startWindowState, startTransaction, callback)
1176         }
1177 
1178         private fun takeOverAnimationInternal(
1179             window: RemoteAnimationTarget,
1180             startWindowState: WindowAnimationState?,
1181             startTransaction: SurfaceControl.Transaction?,
1182             callback: IRemoteAnimationFinishedCallback?,
1183         ) {
1184             val useSpring =
1185                 !controller.isLaunching && startWindowState != null && startTransaction != null
1186             startAnimation(
1187                 window,
1188                 navigationBar = null,
1189                 useSpring,
1190                 startWindowState,
1191                 startTransaction,
1192                 callback,
1193             )
1194         }
1195 
1196         @UiThread
1197         private fun setUpAnimation(
1198             apps: Array<out RemoteAnimationTarget>?,
1199             callback: IRemoteAnimationFinishedCallback?,
1200         ): RemoteAnimationTarget? {
1201             removeTimeouts()
1202 
1203             // The animation was started too late and we already notified the controller that it
1204             // timed out.
1205             if (timedOut) {
1206                 callback?.invoke()
1207                 return null
1208             }
1209 
1210             // This should not happen, but let's make sure we don't start the animation if it was
1211             // cancelled before and we already notified the controller.
1212             if (cancelled) {
1213                 return null
1214             }
1215 
1216             val window = findTargetWindowIfPossible(apps)
1217             if (window == null) {
1218                 Log.i(TAG, "Aborting the animation as no window is opening")
1219                 callback?.invoke()
1220 
1221                 if (DEBUG_TRANSITION_ANIMATION) {
1222                     Log.d(
1223                         TAG,
1224                         "Calling controller.onTransitionAnimationCancelled() [no window opening]",
1225                     )
1226                 }
1227                 controller.onTransitionAnimationCancelled()
1228                 listener?.onTransitionAnimationCancelled()
1229                 return null
1230             }
1231 
1232             return window
1233         }
1234 
1235         private fun findTargetWindowIfPossible(
1236             apps: Array<out RemoteAnimationTarget>?
1237         ): RemoteAnimationTarget? {
1238             if (apps == null) {
1239                 return null
1240             }
1241 
1242             val targetMode =
1243                 if (controller.isLaunching) {
1244                     RemoteAnimationTarget.MODE_OPENING
1245                 } else {
1246                     RemoteAnimationTarget.MODE_CLOSING
1247                 }
1248             var candidate: RemoteAnimationTarget? = null
1249 
1250             for (it in apps) {
1251                 if (it.mode == targetMode) {
1252                     if (activityTransitionUseLargestWindow()) {
1253                         if (returnAnimationsEnabled()) {
1254                             // If the controller contains a cookie, _only_ match if either the
1255                             // candidate contains the matching cookie, or a component is also
1256                             // defined and is a match.
1257                             if (
1258                                 controller.transitionCookie != null &&
1259                                     it.taskInfo
1260                                         ?.launchCookies
1261                                         ?.contains(controller.transitionCookie) != true &&
1262                                     (controller.component == null ||
1263                                         it.taskInfo?.topActivity != controller.component)
1264                             ) {
1265                                 continue
1266                             }
1267                         }
1268 
1269                         if (
1270                             candidate == null ||
1271                                 !it.hasAnimatingParent && candidate.hasAnimatingParent
1272                         ) {
1273                             candidate = it
1274                             continue
1275                         }
1276                         if (
1277                             !it.hasAnimatingParent &&
1278                                 it.screenSpaceBounds.hasGreaterAreaThan(candidate.screenSpaceBounds)
1279                         ) {
1280                             candidate = it
1281                         }
1282                     } else {
1283                         if (!it.hasAnimatingParent) {
1284                             return it
1285                         }
1286                         if (candidate == null) {
1287                             candidate = it
1288                         }
1289                     }
1290                 }
1291             }
1292 
1293             return candidate
1294         }
1295 
1296         private fun startAnimation(
1297             window: RemoteAnimationTarget,
1298             navigationBar: RemoteAnimationTarget? = null,
1299             useSpring: Boolean = false,
1300             startingWindowState: WindowAnimationState? = null,
1301             startTransaction: SurfaceControl.Transaction? = null,
1302             iCallback: IRemoteAnimationFinishedCallback? = null,
1303         ) {
1304             if (TransitionAnimator.DEBUG) {
1305                 Log.d(TAG, "Remote animation started")
1306             }
1307 
1308             val windowBounds = window.screenSpaceBounds
1309             val endState =
1310                 if (controller.isLaunching) {
1311                     controller.windowAnimatorState?.toTransitionState()
1312                         ?: TransitionAnimator.State(
1313                                 top = windowBounds.top,
1314                                 bottom = windowBounds.bottom,
1315                                 left = windowBounds.left,
1316                                 right = windowBounds.right,
1317                             )
1318                             .apply {
1319                                 // TODO(b/184121838): We should somehow get the top and bottom
1320                                 // radius of the window instead of recomputing isExpandingFullyAbove
1321                                 // here.
1322                                 getWindowRadius(
1323                                         transitionAnimator.isExpandingFullyAbove(
1324                                             controller.transitionContainer,
1325                                             this,
1326                                         )
1327                                     )
1328                                     .let {
1329                                         topCornerRadius = it
1330                                         bottomCornerRadius = it
1331                                     }
1332                             }
1333                 } else {
1334                     controller.createAnimatorState()
1335                 }
1336             val windowBackgroundColor =
1337                 if (translucentOccludingActivityFix() && window.isTranslucent) {
1338                     Color.TRANSPARENT
1339                 } else {
1340                     window.taskInfo?.let { callback.getBackgroundColor(it) }
1341                         ?: window.backgroundColor
1342                 }
1343 
1344             val isExpandingFullyAbove =
1345                 transitionAnimator.isExpandingFullyAbove(controller.transitionContainer, endState)
1346             val windowState = startingWindowState ?: controller.windowAnimatorState
1347 
1348             // We animate the opening window and delegate the view expansion to [this.controller].
1349             val delegate = this.controller
1350             val controller =
1351                 object : Controller by delegate {
1352                     override fun createAnimatorState(): TransitionAnimator.State {
1353                         if (isLaunching) {
1354                             return delegate.createAnimatorState()
1355                         } else if (!longLivedReturnAnimationsEnabled()) {
1356                             return delegate.windowAnimatorState?.toTransitionState()
1357                                 ?: getWindowRadius(isExpandingFullyAbove).let {
1358                                     TransitionAnimator.State(
1359                                         top = windowBounds.top,
1360                                         bottom = windowBounds.bottom,
1361                                         left = windowBounds.left,
1362                                         right = windowBounds.right,
1363                                         topCornerRadius = it,
1364                                         bottomCornerRadius = it,
1365                                     )
1366                                 }
1367                         }
1368 
1369                         // TODO(b/323863002): use the timestamp and velocity to update the initial
1370                         //   position.
1371                         val bounds = windowState?.bounds
1372                         val left: Int = bounds?.left?.toInt() ?: windowBounds.left
1373                         val top: Int = bounds?.top?.toInt() ?: windowBounds.top
1374                         val right: Int = bounds?.right?.toInt() ?: windowBounds.right
1375                         val bottom: Int = bounds?.bottom?.toInt() ?: windowBounds.bottom
1376 
1377                         val width = windowBounds.right - windowBounds.left
1378                         val height = windowBounds.bottom - windowBounds.top
1379                         // Scale the window. We use the max of (widthRatio, heightRatio) so that
1380                         // there is no blank space on any side.
1381                         val widthRatio = (right - left).toFloat() / width
1382                         val heightRatio = (bottom - top).toFloat() / height
1383                         val startScale = maxOf(widthRatio, heightRatio)
1384 
1385                         val maybeRadius = windowState?.topLeftRadius
1386                         val windowRadius =
1387                             if (maybeRadius != null) {
1388                                 maybeRadius * startScale
1389                             } else {
1390                                 getWindowRadius(isExpandingFullyAbove)
1391                             }
1392 
1393                         return TransitionAnimator.State(
1394                             top = top,
1395                             bottom = bottom,
1396                             left = left,
1397                             right = right,
1398                             topCornerRadius = windowRadius,
1399                             bottomCornerRadius = windowRadius,
1400                         )
1401                     }
1402 
1403                     override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
1404                         listener?.onTransitionAnimationStart()
1405 
1406                         if (DEBUG_TRANSITION_ANIMATION) {
1407                             Log.d(
1408                                 TAG,
1409                                 "Calling controller.onTransitionAnimationStart(" +
1410                                     "isExpandingFullyAbove=$isExpandingFullyAbove) " +
1411                                     "[controller=$delegate]",
1412                             )
1413                         }
1414 
1415                         if (startTransaction != null) {
1416                             // Calling applyStateToWindow() here avoids skipping a frame when taking
1417                             // over an animation.
1418                             applyStateToWindow(
1419                                 window,
1420                                 createAnimatorState(),
1421                                 linearProgress = 0f,
1422                                 useSpring,
1423                                 startTransaction,
1424                             )
1425                         }
1426 
1427                         delegate.onTransitionAnimationStart(isExpandingFullyAbove)
1428                     }
1429 
1430                     override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
1431                         listener?.onTransitionAnimationEnd()
1432                         iCallback?.invoke()
1433 
1434                         if (DEBUG_TRANSITION_ANIMATION) {
1435                             Log.d(
1436                                 TAG,
1437                                 "Calling controller.onTransitionAnimationEnd(" +
1438                                     "isExpandingFullyAbove=$isExpandingFullyAbove) " +
1439                                     "[controller=$delegate]",
1440                             )
1441                         }
1442                         delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
1443                     }
1444 
1445                     override fun onTransitionAnimationProgress(
1446                         state: TransitionAnimator.State,
1447                         progress: Float,
1448                         linearProgress: Float,
1449                     ) {
1450                         applyStateToWindow(window, state, linearProgress, useSpring)
1451                         navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
1452 
1453                         listener?.onTransitionAnimationProgress(linearProgress)
1454                         delegate.onTransitionAnimationProgress(state, progress, linearProgress)
1455                     }
1456                 }
1457             val velocityPxPerS =
1458                 if (longLivedReturnAnimationsEnabled() && windowState?.velocityPxPerMs != null) {
1459                     val xVelocityPxPerS = windowState.velocityPxPerMs.x * 1000
1460                     val yVelocityPxPerS = windowState.velocityPxPerMs.y * 1000
1461                     PointF(xVelocityPxPerS, yVelocityPxPerS)
1462                 } else if (useSpring) {
1463                     PointF(0f, 0f)
1464                 } else {
1465                     null
1466                 }
1467             animation =
1468                 transitionAnimator.startAnimation(
1469                     controller,
1470                     endState,
1471                     windowBackgroundColor,
1472                     fadeWindowBackgroundLayer = !controller.isBelowAnimatingWindow,
1473                     drawHole = !controller.isBelowAnimatingWindow,
1474                     startVelocity = velocityPxPerS,
1475                     startFrameTime = windowState?.timestamp ?: -1,
1476                 )
1477         }
1478 
1479         private fun getWindowRadius(isExpandingFullyAbove: Boolean): Float {
1480             return if (isExpandingFullyAbove) {
1481                 // Most of the time, expanding fully above the root view means
1482                 // expanding in full screen.
1483                 ScreenDecorationsUtils.getWindowCornerRadius(context)
1484             } else {
1485                 // This usually means we are in split screen mode, so 2 out of 4
1486                 // corners will have a radius of 0.
1487                 0f
1488             }
1489         }
1490 
1491         private fun applyStateToWindow(
1492             window: RemoteAnimationTarget,
1493             state: TransitionAnimator.State,
1494             linearProgress: Float,
1495             useSpring: Boolean,
1496             transaction: SurfaceControl.Transaction? = null,
1497         ) {
1498             if (transactionApplierView.viewRootImpl == null || !window.leash.isValid) {
1499                 // Don't apply any transaction if the view root we synchronize with was detached or
1500                 // if the SurfaceControl associated with [window] is not valid, as
1501                 // [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw.
1502                 return
1503             }
1504 
1505             val screenBounds = window.screenSpaceBounds
1506             val centerX = (screenBounds.left + screenBounds.right) / 2f
1507             val centerY = (screenBounds.top + screenBounds.bottom) / 2f
1508             val width = screenBounds.right - screenBounds.left
1509             val height = screenBounds.bottom - screenBounds.top
1510 
1511             // Scale the window. We use the max of (widthRatio, heightRatio) so that there is no
1512             // blank space on any side.
1513             val widthRatio = state.width.toFloat() / width
1514             val heightRatio = state.height.toFloat() / height
1515             val scale = maxOf(widthRatio, heightRatio)
1516             matrix.reset()
1517             matrix.setScale(scale, scale, centerX, centerY)
1518 
1519             // Align it to the top and center it in the x-axis.
1520             val heightChange = height * scale - height
1521             val translationX = state.centerX - centerX
1522             val translationY = state.top - screenBounds.top + heightChange / 2f
1523             matrix.postTranslate(translationX, translationY)
1524 
1525             // Crop it. The matrix will also be applied to the crop, so we apply the inverse
1526             // operation. Given that we only scale (by factor > 0) then translate, we can assume
1527             // that the matrix is invertible.
1528             val cropX = state.left.toFloat() - screenBounds.left
1529             val cropY = state.top.toFloat() - screenBounds.top
1530             windowCropF.set(cropX, cropY, cropX + state.width, cropY + state.height)
1531             matrix.invert(invertMatrix)
1532             invertMatrix.mapRect(windowCropF)
1533             windowCrop.set(
1534                 windowCropF.left.roundToInt(),
1535                 windowCropF.top.roundToInt(),
1536                 windowCropF.right.roundToInt(),
1537                 windowCropF.bottom.roundToInt(),
1538             )
1539 
1540             val interpolators: TransitionAnimator.Interpolators
1541             val windowProgress: Float
1542 
1543             if (useSpring) {
1544                 val windowAnimationDelay: Float
1545                 val windowAnimationDuration: Float
1546                 if (controller.isLaunching) {
1547                     windowAnimationDelay = SPRING_TIMINGS.contentAfterFadeInDelay
1548                     windowAnimationDuration = SPRING_TIMINGS.contentAfterFadeInDuration
1549                 } else {
1550                     windowAnimationDelay = SPRING_TIMINGS.contentBeforeFadeOutDelay
1551                     windowAnimationDuration = SPRING_TIMINGS.contentBeforeFadeOutDuration
1552                 }
1553 
1554                 interpolators = SPRING_INTERPOLATORS
1555                 windowProgress =
1556                     TransitionAnimator.getProgress(
1557                         linearProgress,
1558                         windowAnimationDelay,
1559                         windowAnimationDuration,
1560                     )
1561             } else {
1562                 val windowAnimationDelay: Long
1563                 val windowAnimationDuration: Long
1564                 if (controller.isLaunching) {
1565                     windowAnimationDelay = TIMINGS.contentAfterFadeInDelay
1566                     windowAnimationDuration = TIMINGS.contentAfterFadeInDuration
1567                 } else {
1568                     windowAnimationDelay = TIMINGS.contentBeforeFadeOutDelay
1569                     windowAnimationDuration = TIMINGS.contentBeforeFadeOutDuration
1570                 }
1571 
1572                 interpolators = INTERPOLATORS
1573                 windowProgress =
1574                     TransitionAnimator.getProgress(
1575                         TIMINGS,
1576                         linearProgress,
1577                         windowAnimationDelay,
1578                         windowAnimationDuration,
1579                     )
1580             }
1581 
1582             // The alpha of the opening window. If it opens above the expandable, then it should
1583             // fade in progressively. Otherwise, it should be fully opaque and will be progressively
1584             // revealed as the window background color layer above the window fades out.
1585             val alpha =
1586                 if (controller.isBelowAnimatingWindow) {
1587                     if (controller.isLaunching) {
1588                         interpolators.contentAfterFadeInInterpolator.getInterpolation(
1589                             windowProgress
1590                         )
1591                     } else {
1592                         1 -
1593                             interpolators.contentBeforeFadeOutInterpolator.getInterpolation(
1594                                 windowProgress
1595                             )
1596                     }
1597                 } else {
1598                     1f
1599                 }
1600 
1601             // The scale will also be applied to the corner radius, so we divide by the scale to
1602             // keep the original radius. We use the max of (topCornerRadius, bottomCornerRadius) to
1603             // make sure that the window does not draw itself behind the expanding view. This is
1604             // especially important for lock screen animations, where the window is not clipped by
1605             // the shade.
1606             val cornerRadius = maxOf(state.topCornerRadius, state.bottomCornerRadius) / scale
1607 
1608             val params =
1609                 SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(window.leash)
1610                     .withAlpha(alpha)
1611                     .withMatrix(matrix)
1612                     .withWindowCrop(windowCrop)
1613                     .withCornerRadius(cornerRadius)
1614                     .withVisibility(true)
1615             if (transaction != null) params.withMergeTransaction(transaction)
1616 
1617             transactionApplier.scheduleApply(params.build())
1618         }
1619 
1620         // TODO(b/377643129): remote transitions have no way of identifying the navbar when
1621         //  converting to RemoteAnimationTargets (and in my testing it was never included in the
1622         //  transition at all). So this method is not used anymore. Remove or adapt once we fully
1623         //  convert to remote transitions.
1624         private fun applyStateToNavigationBar(
1625             navigationBar: RemoteAnimationTarget,
1626             state: TransitionAnimator.State,
1627             linearProgress: Float,
1628         ) {
1629             if (transactionApplierView.viewRootImpl == null || !navigationBar.leash.isValid) {
1630                 // Don't apply any transaction if the view root we synchronize with was detached or
1631                 // if the SurfaceControl associated with [navigationBar] is not valid, as
1632                 // [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw.
1633                 return
1634             }
1635 
1636             val fadeInProgress =
1637                 TransitionAnimator.getProgress(
1638                     TIMINGS,
1639                     linearProgress,
1640                     ANIMATION_DELAY_NAV_FADE_IN,
1641                     ANIMATION_DURATION_NAV_FADE_OUT,
1642                 )
1643 
1644             val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(navigationBar.leash)
1645             if (fadeInProgress > 0) {
1646                 matrix.reset()
1647                 matrix.setTranslate(
1648                     0f,
1649                     (state.top - navigationBar.sourceContainerBounds.top).toFloat(),
1650                 )
1651                 windowCrop.set(state.left, 0, state.right, state.height)
1652                 params
1653                     .withAlpha(NAV_FADE_IN_INTERPOLATOR.getInterpolation(fadeInProgress))
1654                     .withMatrix(matrix)
1655                     .withWindowCrop(windowCrop)
1656                     .withVisibility(true)
1657             } else {
1658                 val fadeOutProgress =
1659                     TransitionAnimator.getProgress(
1660                         TIMINGS,
1661                         linearProgress,
1662                         0,
1663                         ANIMATION_DURATION_NAV_FADE_OUT,
1664                     )
1665                 params.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress))
1666             }
1667 
1668             transactionApplier.scheduleApply(params.build())
1669         }
1670 
1671         private fun onAnimationTimedOut() {
1672             // The remote animation was cancelled by WM, so we already cancelled the transition
1673             // animation.
1674             if (cancelled) {
1675                 return
1676             }
1677 
1678             Log.w(TAG, "Remote animation timed out")
1679             timedOut = true
1680 
1681             if (DEBUG_TRANSITION_ANIMATION) {
1682                 Log.d(
1683                     TAG,
1684                     "Calling controller.onTransitionAnimationCancelled() [animation timed out]",
1685                 )
1686             }
1687             controller.onTransitionAnimationCancelled()
1688             listener?.onTransitionAnimationCancelled()
1689         }
1690 
1691         @UiThread
1692         override fun onAnimationCancelled() {
1693             removeTimeouts()
1694 
1695             // The short timeout happened, so we already cancelled the transition animation.
1696             if (timedOut) {
1697                 return
1698             }
1699 
1700             Log.i(TAG, "Remote animation was cancelled")
1701             cancelled = true
1702 
1703             animation?.cancel()
1704 
1705             if (DEBUG_TRANSITION_ANIMATION) {
1706                 Log.d(
1707                     TAG,
1708                     "Calling controller.onTransitionAnimationCancelled() [remote animation " +
1709                         "cancelled]",
1710                 )
1711             }
1712             controller.onTransitionAnimationCancelled()
1713             listener?.onTransitionAnimationCancelled()
1714         }
1715 
1716         private fun IRemoteAnimationFinishedCallback.invoke() {
1717             try {
1718                 onAnimationFinished()
1719             } catch (e: RemoteException) {
1720                 e.printStackTrace()
1721             }
1722         }
1723 
1724         private fun Rect.hasGreaterAreaThan(other: Rect): Boolean {
1725             return (this.width() * this.height()) > (other.width() * other.height())
1726         }
1727     }
1728 
1729     /**
1730      * Wraps one of the two methods we have to register remote transitions with WM Shell:
1731      * - for in-process registrations (e.g. System UI) we use [ShellTransitions]
1732      * - for cross-process registrations (e.g. Launcher) we use [IShellTransitions]
1733      *
1734      * Important: each instance of this class must wrap exactly one of the two.
1735      */
1736     class TransitionRegister
1737     private constructor(
1738         private val shellTransitions: ShellTransitions? = null,
1739         private val iShellTransitions: IShellTransitions? = null,
1740     ) {
1741         init {
1742             assert((shellTransitions != null).xor(iShellTransitions != null))
1743         }
1744 
1745         companion object {
1746             /** Provides a [TransitionRegister] instance wrapping [ShellTransitions]. */
1747             fun fromShellTransitions(shellTransitions: ShellTransitions): TransitionRegister {
1748                 return TransitionRegister(shellTransitions = shellTransitions)
1749             }
1750 
1751             /** Provides a [TransitionRegister] instance wrapping [IShellTransitions]. */
1752             fun fromIShellTransitions(iShellTransitions: IShellTransitions): TransitionRegister {
1753                 return TransitionRegister(iShellTransitions = iShellTransitions)
1754             }
1755         }
1756 
1757         /** Register [remoteTransition] with WM Shell using the given [filter]. */
1758         internal fun register(
1759             filter: TransitionFilter,
1760             remoteTransition: RemoteTransition,
1761             includeTakeover: Boolean,
1762         ) {
1763             shellTransitions?.registerRemote(filter, remoteTransition)
1764             iShellTransitions?.registerRemote(filter, remoteTransition)
1765             if (includeTakeover) {
1766                 shellTransitions?.registerRemoteForTakeover(filter, remoteTransition)
1767                 iShellTransitions?.registerRemoteForTakeover(filter, remoteTransition)
1768             }
1769         }
1770 
1771         /** Unregister [remoteTransition] from WM Shell. */
1772         internal fun unregister(remoteTransition: RemoteTransition) {
1773             shellTransitions?.unregisterRemote(remoteTransition)
1774             iShellTransitions?.unregisterRemote(remoteTransition)
1775         }
1776     }
1777 
1778     /**
1779      * A cookie used to uniquely identify a task launched using an
1780      * [ActivityTransitionAnimator.Controller].
1781      *
1782      * The [String] encapsulated by this class should be formatted in such a way to be unique across
1783      * the system, but reliably constant for the same associated launchable.
1784      *
1785      * Recommended naming scheme:
1786      * - DO use the fully qualified name of the class that owns the instance of the launchable,
1787      *   along with a concise and precise description of the purpose of the launchable in question.
1788      * - DO NOT introduce uniqueness through the use of timestamps or other runtime variables that
1789      *   will change if the instance is destroyed and re-created.
1790      *
1791      * Example: "com.not.the.real.class.name.ShadeController_openSettingsButton"
1792      *
1793      * Note that sometimes (e.g. in recycler views) there could be multiple instances of the same
1794      * launchable, and no static knowledge to adequately differentiate between them using a single
1795      * description. In this case, the recommendation is to append a unique identifier related to the
1796      * contents of the launchable.
1797      *
1798      * Example: “com.not.the.real.class.name.ToastWebResult_launchAga_id143256”
1799      */
1800     data class TransitionCookie(private val cookie: String) : Binder()
1801 }
1802