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