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