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