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