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