1 /* <lambda>null2 * Copyright (C) 2024 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.statusbar.phone 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.TaskStackBuilder 24 import android.content.Context 25 import android.content.Intent 26 import android.os.Bundle 27 import android.os.RemoteException 28 import android.os.UserHandle 29 import android.provider.Settings 30 import android.util.Log 31 import android.view.RemoteAnimationAdapter 32 import android.view.View 33 import android.view.WindowManager 34 import com.android.keyguard.KeyguardUpdateMonitor 35 import com.android.systemui.ActivityIntentHelper 36 import com.android.systemui.Flags.communalHub 37 import com.android.systemui.animation.ActivityTransitionAnimator 38 import com.android.systemui.animation.DelegateTransitionAnimatorController 39 import com.android.systemui.assist.AssistManager 40 import com.android.systemui.camera.CameraIntents 41 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor 42 import com.android.systemui.communal.shared.model.CommunalScenes 43 import com.android.systemui.dagger.SysUISingleton 44 import com.android.systemui.dagger.qualifiers.DisplayId 45 import com.android.systemui.dagger.qualifiers.Main 46 import com.android.systemui.keyguard.KeyguardViewMediator 47 import com.android.systemui.keyguard.WakefulnessLifecycle 48 import com.android.systemui.plugins.ActivityStarter 49 import com.android.systemui.res.R 50 import com.android.systemui.settings.UserTracker 51 import com.android.systemui.shade.ShadeController 52 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor 53 import com.android.systemui.statusbar.CommandQueue 54 import com.android.systemui.statusbar.NotificationLockscreenUserManager 55 import com.android.systemui.statusbar.NotificationShadeWindowController 56 import com.android.systemui.statusbar.SysuiStatusBarStateController 57 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow 58 import com.android.systemui.statusbar.policy.DeviceProvisionedController 59 import com.android.systemui.statusbar.policy.KeyguardStateController 60 import com.android.systemui.statusbar.window.StatusBarWindowController 61 import com.android.systemui.util.concurrency.DelayableExecutor 62 import com.android.systemui.util.kotlin.getOrNull 63 import dagger.Lazy 64 import java.util.Optional 65 import javax.inject.Inject 66 67 /** Encapsulates the activity logic for activity starter. */ 68 @SysUISingleton 69 class LegacyActivityStarterInternalImpl 70 @Inject 71 constructor( 72 private val centralSurfacesOptLazy: Lazy<Optional<CentralSurfaces>>, 73 private val keyguardStateController: KeyguardStateController, 74 private val statusBarStateController: SysuiStatusBarStateController, 75 private val assistManagerLazy: Lazy<AssistManager>, 76 private val dozeServiceHostLazy: Lazy<DozeServiceHost>, 77 private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>, 78 private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>, 79 private val shadeControllerLazy: Lazy<ShadeController>, 80 private val commandQueue: CommandQueue, 81 private val shadeAnimationInteractor: ShadeAnimationInteractor, 82 private val statusBarKeyguardViewManagerLazy: Lazy<StatusBarKeyguardViewManager>, 83 private val notifShadeWindowControllerLazy: Lazy<NotificationShadeWindowController>, 84 private val activityTransitionAnimator: ActivityTransitionAnimator, 85 private val context: Context, 86 @DisplayId private val displayId: Int, 87 private val lockScreenUserManager: NotificationLockscreenUserManager, 88 private val statusBarWindowController: StatusBarWindowController, 89 private val wakefulnessLifecycle: WakefulnessLifecycle, 90 private val keyguardUpdateMonitor: KeyguardUpdateMonitor, 91 private val deviceProvisionedController: DeviceProvisionedController, 92 private val userTracker: UserTracker, 93 private val activityIntentHelper: ActivityIntentHelper, 94 @Main private val mainExecutor: DelayableExecutor, 95 private val communalSceneInteractor: CommunalSceneInteractor, 96 ) : ActivityStarterInternal { 97 private val centralSurfaces: CentralSurfaces? 98 get() = centralSurfacesOptLazy.get().getOrNull() 99 100 override fun startActivityDismissingKeyguard( 101 intent: Intent, 102 dismissShade: Boolean, 103 onlyProvisioned: Boolean, 104 callback: ActivityStarter.Callback?, 105 flags: Int, 106 animationController: ActivityTransitionAnimator.Controller?, 107 customMessage: String?, 108 disallowEnterPictureInPictureWhileLaunching: Boolean, 109 userHandle: UserHandle?, 110 ) { 111 val userHandle: UserHandle = userHandle ?: getActivityUserHandle(intent) 112 113 if (onlyProvisioned && !deviceProvisionedController.isDeviceProvisioned) return 114 115 val willLaunchResolverActivity: Boolean = 116 activityIntentHelper.wouldLaunchResolverActivity( 117 intent, 118 lockScreenUserManager.currentUserId 119 ) 120 121 val animate = 122 animationController != null && 123 !willLaunchResolverActivity && 124 shouldAnimateLaunch(isActivityIntent = true) 125 val animController = 126 wrapAnimationControllerForShadeOrStatusBar( 127 animationController = animationController, 128 dismissShade = dismissShade, 129 isLaunchForActivity = true, 130 ) 131 132 // If we animate, we will dismiss the shade only once the animation is done. This is 133 // taken care of by the StatusBarLaunchAnimationController. 134 val dismissShadeDirectly = dismissShade && animController == null 135 136 val runnable = Runnable { 137 assistManagerLazy.get().hideAssist() 138 intent.flags = 139 if (intent.flags and Intent.FLAG_ACTIVITY_REORDER_TO_FRONT != 0) { 140 Intent.FLAG_ACTIVITY_NEW_TASK 141 } else { 142 Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP 143 } 144 intent.addFlags(flags) 145 val result = intArrayOf(ActivityManager.START_CANCELED) 146 activityTransitionAnimator.startIntentWithAnimation( 147 animController, 148 animate, 149 intent.getPackage() 150 ) { adapter: RemoteAnimationAdapter? -> 151 val options = 152 ActivityOptions(CentralSurfaces.getActivityOptions(displayId, adapter)) 153 154 // We know that the intent of the caller is to dismiss the keyguard and 155 // this runnable is called right after the keyguard is solved, so we tell 156 // WM that we should dismiss it to avoid flickers when opening an activity 157 // that can also be shown over the keyguard. 158 options.setDismissKeyguardIfInsecure() 159 options.setDisallowEnterPictureInPictureWhileLaunching( 160 disallowEnterPictureInPictureWhileLaunching 161 ) 162 if (CameraIntents.isInsecureCameraIntent(intent)) { 163 // Normally an activity will set it's requested rotation 164 // animation on its window. However when launching an activity 165 // causes the orientation to change this is too late. In these cases 166 // the default animation is used. This doesn't look good for 167 // the camera (as it rotates the camera contents out of sync 168 // with physical reality). So, we ask the WindowManager to 169 // force the cross fade animation if an orientation change 170 // happens to occur during the launch. 171 options.rotationAnimationHint = 172 WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS 173 } 174 if (Settings.Panel.ACTION_VOLUME == intent.action) { 175 // Settings Panel is implemented as activity(not a dialog), so 176 // underlying app is paused and may enter picture-in-picture mode 177 // as a result. 178 // So we need to disable picture-in-picture mode here 179 // if it is volume panel. 180 options.setDisallowEnterPictureInPictureWhileLaunching(true) 181 } 182 try { 183 result[0] = 184 ActivityTaskManager.getService() 185 .startActivityAsUser( 186 null, 187 context.basePackageName, 188 context.attributionTag, 189 intent, 190 intent.resolveTypeIfNeeded(context.contentResolver), 191 null, 192 null, 193 0, 194 Intent.FLAG_ACTIVITY_NEW_TASK, 195 null, 196 options.toBundle(), 197 userHandle.identifier, 198 ) 199 } catch (e: RemoteException) { 200 Log.w(TAG, "Unable to start activity", e) 201 } 202 result[0] 203 } 204 callback?.onActivityStarted(result[0]) 205 } 206 val cancelRunnable = Runnable { 207 callback?.onActivityStarted(ActivityManager.START_CANCELED) 208 } 209 // Do not deferKeyguard when occluded because, when keyguard is occluded, 210 // we do not launch the activity until keyguard is done. 211 val occluded = (keyguardStateController.isShowing && keyguardStateController.isOccluded) 212 val deferred = !occluded 213 executeRunnableDismissingKeyguard( 214 runnable, 215 cancelRunnable, 216 dismissShadeDirectly, 217 willLaunchResolverActivity, 218 deferred, 219 animate, 220 customMessage, 221 ) 222 } 223 224 override fun startPendingIntentDismissingKeyguard( 225 intent: PendingIntent, 226 dismissShade: Boolean, 227 intentSentUiThreadCallback: Runnable?, 228 associatedView: View?, 229 animationController: ActivityTransitionAnimator.Controller?, 230 showOverLockscreen: Boolean, 231 skipLockscreenChecks: Boolean, 232 fillInIntent: Intent?, 233 extraOptions: Bundle?, 234 ) { 235 val animationController = 236 if (associatedView is ExpandableNotificationRow) { 237 centralSurfaces?.getAnimatorControllerFromNotification(associatedView) 238 } else animationController 239 240 val willLaunchResolverActivity = 241 (intent.isActivity && 242 activityIntentHelper.wouldPendingLaunchResolverActivity( 243 intent, 244 lockScreenUserManager.currentUserId, 245 )) 246 247 val actuallyShowOverLockscreen = 248 showOverLockscreen && 249 intent.isActivity && 250 (skipLockscreenChecks || activityIntentHelper.wouldPendingShowOverLockscreen( 251 intent, 252 lockScreenUserManager.currentUserId 253 )) 254 255 val animate = 256 !willLaunchResolverActivity && 257 animationController != null && 258 shouldAnimateLaunch(intent.isActivity, actuallyShowOverLockscreen) 259 260 // We wrap animationCallback with a StatusBarLaunchAnimatorController so 261 // that the shade is collapsed after the animation (or when it is cancelled, 262 // aborted, etc). 263 val statusBarController = 264 wrapAnimationControllerForShadeOrStatusBar( 265 animationController = animationController, 266 dismissShade = dismissShade, 267 isLaunchForActivity = intent.isActivity, 268 ) 269 val controller = 270 if (actuallyShowOverLockscreen) { 271 wrapAnimationControllerForLockscreen(dismissShade, statusBarController) 272 } else { 273 statusBarController 274 } 275 276 // If we animate, don't collapse the shade and defer the keyguard dismiss (in case we 277 // run the animation on the keyguard). The animation will take care of (instantly) 278 // collapsing the shade and hiding the keyguard once it is done. 279 val collapse = dismissShade && !animate 280 val runnable = Runnable { 281 try { 282 activityTransitionAnimator.startPendingIntentWithAnimation( 283 controller, 284 animate, 285 intent.creatorPackage, 286 actuallyShowOverLockscreen, 287 object : ActivityTransitionAnimator.PendingIntentStarter { 288 override fun startPendingIntent( 289 animationAdapter: RemoteAnimationAdapter? 290 ): Int { 291 val options = 292 ActivityOptions( 293 CentralSurfaces.getActivityOptions(displayId, animationAdapter) 294 .apply { extraOptions?.let { putAll(it) } } 295 ) 296 // TODO b/221255671: restrict this to only be set for 297 // notifications 298 options.isEligibleForLegacyPermissionPrompt = true 299 options.setPendingIntentBackgroundActivityStartMode( 300 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED 301 ) 302 return intent.sendAndReturnResult( 303 context, 304 0, 305 fillInIntent, 306 null, 307 null, 308 null, 309 options.toBundle() 310 ) 311 } 312 }, 313 ) 314 } catch (e: PendingIntent.CanceledException) { 315 // the stack trace isn't very helpful here. 316 // Just log the exception message. 317 Log.w(TAG, "Sending intent failed: $e") 318 if (!collapse) { 319 // executeRunnableDismissingKeyguard did not collapse for us already. 320 shadeControllerLazy.get().collapseOnMainThread() 321 } 322 // TODO: Dismiss Keyguard. 323 } 324 if (intent.isActivity) { 325 assistManagerLazy.get().hideAssist() 326 // This activity could have started while the device is dreaming, in which case 327 // the dream would occlude the activity. In order to show the newly started 328 // activity, we wake from the dream. 329 keyguardUpdateMonitor.awakenFromDream() 330 } 331 intentSentUiThreadCallback?.let { postOnUiThread(runnable = it) } 332 } 333 334 if (!actuallyShowOverLockscreen) { 335 postOnUiThread(delay = 0) { 336 executeRunnableDismissingKeyguard( 337 runnable = runnable, 338 afterKeyguardGone = willLaunchResolverActivity, 339 dismissShade = collapse, 340 willAnimateOnKeyguard = animate, 341 ) 342 } 343 } else { 344 postOnUiThread(delay = 0, runnable) 345 } 346 } 347 348 override fun startActivity( 349 intent: Intent, 350 dismissShade: Boolean, 351 animationController: ActivityTransitionAnimator.Controller?, 352 showOverLockscreenWhenLocked: Boolean, 353 userHandle: UserHandle?, 354 ) { 355 val userHandle = userHandle ?: getActivityUserHandle(intent) 356 // Make sure that we dismiss the keyguard if it is directly dismissible or when we don't 357 // want to show the activity above it. 358 if (keyguardStateController.isUnlocked || !showOverLockscreenWhenLocked) { 359 startActivityDismissingKeyguard( 360 intent = intent, 361 onlyProvisioned = false, 362 dismissShade = dismissShade, 363 disallowEnterPictureInPictureWhileLaunching = false, 364 callback = null, 365 flags = 0, 366 animationController = animationController, 367 userHandle = userHandle, 368 ) 369 return 370 } 371 372 val animate = 373 animationController != null && 374 shouldAnimateLaunch(/* isActivityIntent= */ true, showOverLockscreenWhenLocked) 375 376 var controller: ActivityTransitionAnimator.Controller? = null 377 if (animate) { 378 // Wrap the animation controller to dismiss the shade and set 379 // mIsLaunchingActivityOverLockscreen during the animation. 380 val delegate = 381 wrapAnimationControllerForShadeOrStatusBar( 382 animationController = animationController, 383 dismissShade = dismissShade, 384 isLaunchForActivity = true, 385 ) 386 controller = wrapAnimationControllerForLockscreen(dismissShade, delegate) 387 } else if (dismissShade) { 388 // The animation will take care of dismissing the shade at the end of the animation. 389 // If we don't animate, collapse it directly. 390 shadeControllerLazy.get().cancelExpansionAndCollapseShade() 391 } 392 393 // We should exit the dream to prevent the activity from starting below the 394 // dream. 395 if (keyguardUpdateMonitor.isDreaming) { 396 centralSurfaces?.awakenDreams() 397 } 398 399 activityTransitionAnimator.startIntentWithAnimation( 400 controller, 401 animate, 402 intent.getPackage(), 403 showOverLockscreenWhenLocked 404 ) { adapter: RemoteAnimationAdapter? -> 405 TaskStackBuilder.create(context) 406 .addNextIntent(intent) 407 .startActivities(CentralSurfaces.getActivityOptions(displayId, adapter), userHandle) 408 } 409 } 410 411 override fun dismissKeyguardThenExecute( 412 action: ActivityStarter.OnDismissAction, 413 cancel: Runnable?, 414 afterKeyguardGone: Boolean, 415 customMessage: String?, 416 ) { 417 Log.i(TAG, "Invoking dismissKeyguardThenExecute, afterKeyguardGone: $afterKeyguardGone") 418 if ( 419 !action.willRunAnimationOnKeyguard() && 420 wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP && 421 keyguardStateController.canDismissLockScreen() && 422 !statusBarStateController.leaveOpenOnKeyguardHide() && 423 dozeServiceHostLazy.get().isPulsing 424 ) { 425 // Reuse the biometric wake-and-unlock transition if we dismiss keyguard from a 426 // pulse. 427 // TODO (b/338578036): Factor this transition out of BiometricUnlockController. 428 biometricUnlockControllerLazy 429 .get() 430 .startWakeAndUnlock(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING, null) 431 } 432 if (keyguardStateController.isShowing) { 433 statusBarKeyguardViewManagerLazy 434 .get() 435 .dismissWithAction(action, cancel, afterKeyguardGone, customMessage) 436 } else { 437 // If the keyguard isn't showing but the device is dreaming, we should exit the 438 // dream. 439 if (keyguardUpdateMonitor.isDreaming) { 440 centralSurfaces?.awakenDreams() 441 } 442 action.onDismiss() 443 } 444 } 445 446 override fun executeRunnableDismissingKeyguard( 447 runnable: Runnable?, 448 cancelAction: Runnable?, 449 dismissShade: Boolean, 450 afterKeyguardGone: Boolean, 451 deferred: Boolean, 452 willAnimateOnKeyguard: Boolean, 453 customMessage: String?, 454 ) { 455 val onDismissAction: ActivityStarter.OnDismissAction = 456 object : ActivityStarter.OnDismissAction { 457 override fun onDismiss(): Boolean { 458 if (runnable != null) { 459 if ( 460 keyguardStateController.isShowing && keyguardStateController.isOccluded 461 ) { 462 statusBarKeyguardViewManagerLazy 463 .get() 464 .addAfterKeyguardGoneRunnable(runnable) 465 } else { 466 mainExecutor.execute(runnable) 467 } 468 } 469 if (dismissShade) { 470 shadeControllerLazy.get().collapseShadeForActivityStart() 471 } 472 if (communalHub()) { 473 communalSceneInteractor.snapToSceneForActivityStart(CommunalScenes.Blank) 474 } 475 return deferred 476 } 477 478 override fun willRunAnimationOnKeyguard(): Boolean { 479 return willAnimateOnKeyguard 480 } 481 } 482 dismissKeyguardThenExecute( 483 onDismissAction, 484 cancelAction, 485 afterKeyguardGone, 486 customMessage, 487 ) 488 } 489 490 /** 491 * Return a [ActivityTransitionAnimator.Controller] wrapping `animationController` so that: 492 * - if it launches in the notification shade window and `dismissShade` is true, then the shade 493 * will be instantly dismissed at the end of the animation. 494 * - if it launches in status bar window, it will make the status bar window match the device 495 * size during the animation (that way, the animation won't be clipped by the status bar 496 * size). 497 * 498 * @param animationController the controller that is wrapped and will drive the main animation. 499 * @param dismissShade whether the notification shade will be dismissed at the end of the 500 * animation. This is ignored if `animationController` is not animating in the shade window. 501 * @param isLaunchForActivity whether the launch is for an activity. 502 */ 503 private fun wrapAnimationControllerForShadeOrStatusBar( 504 animationController: ActivityTransitionAnimator.Controller?, 505 dismissShade: Boolean, 506 isLaunchForActivity: Boolean, 507 ): ActivityTransitionAnimator.Controller? { 508 if (animationController == null) { 509 return null 510 } 511 val rootView = animationController.transitionContainer.rootView 512 val controllerFromStatusBar: Optional<ActivityTransitionAnimator.Controller> = 513 statusBarWindowController.wrapAnimationControllerIfInStatusBar( 514 rootView, 515 animationController 516 ) 517 if (controllerFromStatusBar.isPresent) { 518 return controllerFromStatusBar.get() 519 } 520 521 centralSurfaces?.let { 522 // If the view is not in the status bar, then we are animating a view in the shade. 523 // We have to make sure that we collapse it when the animation ends or is cancelled. 524 if (dismissShade) { 525 return StatusBarTransitionAnimatorController( 526 animationController, 527 shadeAnimationInteractor, 528 shadeControllerLazy.get(), 529 notifShadeWindowControllerLazy.get(), 530 commandQueue, 531 displayId, 532 isLaunchForActivity 533 ) 534 } 535 } 536 537 return animationController 538 } 539 540 /** 541 * Wraps an animation controller so that if an activity would be launched on top of the 542 * lockscreen, the correct flags are set for it to be occluded. 543 */ 544 private fun wrapAnimationControllerForLockscreen( 545 dismissShade: Boolean, 546 animationController: ActivityTransitionAnimator.Controller? 547 ): ActivityTransitionAnimator.Controller? { 548 return animationController?.let { 549 object : DelegateTransitionAnimatorController(it) { 550 override fun onIntentStarted(willAnimate: Boolean) { 551 delegate.onIntentStarted(willAnimate) 552 if (willAnimate) { 553 centralSurfaces?.setIsLaunchingActivityOverLockscreen(true, dismissShade) 554 } 555 } 556 557 override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { 558 super.onTransitionAnimationStart(isExpandingFullyAbove) 559 if (communalHub()) { 560 communalSceneInteractor.snapToSceneForActivityStart( 561 CommunalScenes.Blank, 562 ActivityTransitionAnimator.TIMINGS.totalDuration 563 ) 564 } 565 // Double check that the keyguard is still showing and not going 566 // away, but if so set the keyguard occluded. Typically, WM will let 567 // KeyguardViewMediator know directly, but we're overriding that to 568 // play the custom launch animation, so we need to take care of that 569 // here. The unocclude animation is not overridden, so WM will call 570 // KeyguardViewMediator's unocclude animation runner when the 571 // activity is exited. 572 if ( 573 keyguardStateController.isShowing && 574 !keyguardStateController.isKeyguardGoingAway 575 ) { 576 Log.d(TAG, "Setting occluded = true in #startActivity.") 577 keyguardViewMediatorLazy 578 .get() 579 .setOccluded(true /* isOccluded */, true /* animate */) 580 } 581 } 582 583 override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { 584 // Set mIsLaunchingActivityOverLockscreen to false before actually 585 // finishing the animation so that we can assume that 586 // mIsLaunchingActivityOverLockscreen being true means that we will 587 // collapse the shade (or at least run the post collapse runnables) 588 // later on. 589 centralSurfaces?.setIsLaunchingActivityOverLockscreen(false, false) 590 delegate.onTransitionAnimationEnd(isExpandingFullyAbove) 591 } 592 593 override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) { 594 if (newKeyguardOccludedState != null) { 595 keyguardViewMediatorLazy 596 .get() 597 .setOccluded(newKeyguardOccludedState, false /* animate */) 598 } 599 600 // Set mIsLaunchingActivityOverLockscreen to false before actually 601 // finishing the animation so that we can assume that 602 // mIsLaunchingActivityOverLockscreen being true means that we will 603 // collapse the shade (or at least run the // post collapse 604 // runnables) later on. 605 centralSurfaces?.setIsLaunchingActivityOverLockscreen(false, false) 606 delegate.onTransitionAnimationCancelled(newKeyguardOccludedState) 607 } 608 } 609 } 610 } 611 612 /** Retrieves the current user handle to start the Activity. */ 613 private fun getActivityUserHandle(intent: Intent): UserHandle { 614 val packages: Array<String> = context.resources.getStringArray(R.array.system_ui_packages) 615 for (pkg in packages) { 616 val componentName = intent.component ?: break 617 if (pkg == componentName.packageName) { 618 return UserHandle(UserHandle.myUserId()) 619 } 620 } 621 return userTracker.userHandle 622 } 623 624 /** 625 * Whether we should animate an activity launch. 626 * 627 * Note: This method must be called *before* dismissing the keyguard. 628 */ 629 private fun shouldAnimateLaunch( 630 isActivityIntent: Boolean, 631 showOverLockscreen: Boolean, 632 ): Boolean { 633 // TODO(b/294418322): Support launch animations when occluded. 634 if (keyguardStateController.isOccluded) { 635 return false 636 } 637 638 // Always animate if we are not showing the keyguard or if we animate over the lockscreen 639 // (without unlocking it). 640 if (showOverLockscreen || !keyguardStateController.isShowing) { 641 return true 642 } 643 644 // We don't animate non-activity launches as they can break the animation. 645 // TODO(b/184121838): Support non activity launches on the lockscreen. 646 return isActivityIntent 647 } 648 649 override fun shouldAnimateLaunch(isActivityIntent: Boolean): Boolean { 650 return shouldAnimateLaunch(isActivityIntent, false) 651 } 652 653 private fun postOnUiThread(delay: Int = 0, runnable: Runnable) { 654 mainExecutor.executeDelayed(runnable, delay.toLong()) 655 } 656 657 companion object { 658 private const val TAG = "LegacyActivityStarterInternalImpl" 659 } 660 } 661