1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.systemui.statusbar.phone
16 
17 import android.app.ActivityManager
18 import android.app.ActivityOptions
19 import android.app.ActivityTaskManager
20 import android.app.PendingIntent
21 import android.app.TaskStackBuilder
22 import android.content.Context
23 import android.content.Intent
24 import android.os.RemoteException
25 import android.os.UserHandle
26 import android.provider.Settings
27 import android.util.Log
28 import android.view.RemoteAnimationAdapter
29 import android.view.View
30 import android.view.WindowManager
31 import com.android.keyguard.KeyguardUpdateMonitor
32 import com.android.systemui.ActivityIntentHelper
33 import com.android.systemui.R
34 import com.android.systemui.animation.ActivityLaunchAnimator
35 import com.android.systemui.animation.ActivityLaunchAnimator.PendingIntentStarter
36 import com.android.systemui.animation.DelegateLaunchAnimatorController
37 import com.android.systemui.assist.AssistManager
38 import com.android.systemui.camera.CameraIntents.Companion.isInsecureCameraIntent
39 import com.android.systemui.dagger.SysUISingleton
40 import com.android.systemui.dagger.qualifiers.DisplayId
41 import com.android.systemui.dagger.qualifiers.Main
42 import com.android.systemui.keyguard.KeyguardViewMediator
43 import com.android.systemui.keyguard.WakefulnessLifecycle
44 import com.android.systemui.plugins.ActivityStarter
45 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
46 import com.android.systemui.settings.UserTracker
47 import com.android.systemui.shade.ShadeController
48 import com.android.systemui.shade.ShadeViewController
49 import com.android.systemui.statusbar.NotificationLockscreenUserManager
50 import com.android.systemui.statusbar.NotificationShadeWindowController
51 import com.android.systemui.statusbar.SysuiStatusBarStateController
52 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
53 import com.android.systemui.statusbar.policy.DeviceProvisionedController
54 import com.android.systemui.statusbar.policy.KeyguardStateController
55 import com.android.systemui.statusbar.window.StatusBarWindowController
56 import com.android.systemui.util.concurrency.DelayableExecutor
57 import com.android.systemui.util.kotlin.getOrNull
58 import dagger.Lazy
59 import java.util.Optional
60 import javax.inject.Inject
61 
62 /** Handles start activity logic in SystemUI. */
63 @SysUISingleton
64 class ActivityStarterImpl
65 @Inject
66 constructor(
67     private val centralSurfacesOptLazy: Lazy<Optional<CentralSurfaces>>,
68     private val assistManagerLazy: Lazy<AssistManager>,
69     private val dozeServiceHostLazy: Lazy<DozeServiceHost>,
70     private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
71     private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
72     private val shadeControllerLazy: Lazy<ShadeController>,
73     private val shadeViewControllerLazy: Lazy<ShadeViewController>,
74     private val statusBarKeyguardViewManagerLazy: Lazy<StatusBarKeyguardViewManager>,
75     private val notifShadeWindowControllerLazy: Lazy<NotificationShadeWindowController>,
76     private val activityLaunchAnimator: ActivityLaunchAnimator,
77     private val context: Context,
78     @DisplayId private val displayId: Int,
79     private val lockScreenUserManager: NotificationLockscreenUserManager,
80     private val statusBarWindowController: StatusBarWindowController,
81     private val wakefulnessLifecycle: WakefulnessLifecycle,
82     private val keyguardStateController: KeyguardStateController,
83     private val statusBarStateController: SysuiStatusBarStateController,
84     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
85     private val deviceProvisionedController: DeviceProvisionedController,
86     private val userTracker: UserTracker,
87     private val activityIntentHelper: ActivityIntentHelper,
88     @Main private val mainExecutor: DelayableExecutor,
89 ) : ActivityStarter {
90     companion object {
91         const val TAG = "ActivityStarterImpl"
92     }
93 
94     private val centralSurfaces: CentralSurfaces?
95         get() = centralSurfacesOptLazy.get().getOrNull()
96 
97     private val activityStarterInternal = ActivityStarterInternal()
98 
99     override fun startPendingIntentDismissingKeyguard(intent: PendingIntent) {
100         activityStarterInternal.startPendingIntentDismissingKeyguard(intent = intent)
101     }
102 
103     override fun startPendingIntentDismissingKeyguard(
104         intent: PendingIntent,
105         intentSentUiThreadCallback: Runnable?,
106     ) {
107         activityStarterInternal.startPendingIntentDismissingKeyguard(
108             intent = intent,
109             intentSentUiThreadCallback = intentSentUiThreadCallback,
110         )
111     }
112 
113     override fun startPendingIntentDismissingKeyguard(
114         intent: PendingIntent,
115         intentSentUiThreadCallback: Runnable?,
116         associatedView: View?,
117     ) {
118         activityStarterInternal.startPendingIntentDismissingKeyguard(
119             intent = intent,
120             intentSentUiThreadCallback = intentSentUiThreadCallback,
121             associatedView = associatedView,
122         )
123     }
124 
125     override fun startPendingIntentDismissingKeyguard(
126         intent: PendingIntent,
127         intentSentUiThreadCallback: Runnable?,
128         animationController: ActivityLaunchAnimator.Controller?,
129     ) {
130         activityStarterInternal.startPendingIntentDismissingKeyguard(
131             intent = intent,
132             intentSentUiThreadCallback = intentSentUiThreadCallback,
133             animationController = animationController,
134         )
135     }
136 
137     /**
138      * TODO(b/279084380): Change callers to just call startActivityDismissingKeyguard and deprecate
139      *   this.
140      */
141     override fun startActivity(intent: Intent, dismissShade: Boolean) {
142         activityStarterInternal.startActivityDismissingKeyguard(
143             intent = intent,
144             dismissShade = dismissShade,
145         )
146     }
147 
148     /**
149      * TODO(b/279084380): Change callers to just call startActivityDismissingKeyguard and deprecate
150      *   this.
151      */
152     override fun startActivity(intent: Intent, onlyProvisioned: Boolean, dismissShade: Boolean) {
153         activityStarterInternal.startActivityDismissingKeyguard(
154             intent = intent,
155             onlyProvisioned = onlyProvisioned,
156             dismissShade = dismissShade,
157         )
158     }
159 
160     /**
161      * TODO(b/279084380): Change callers to just call startActivityDismissingKeyguard and deprecate
162      *   this.
163      */
164     override fun startActivity(
165         intent: Intent,
166         dismissShade: Boolean,
167         callback: ActivityStarter.Callback?,
168     ) {
169         activityStarterInternal.startActivityDismissingKeyguard(
170             intent = intent,
171             dismissShade = dismissShade,
172             callback = callback,
173         )
174     }
175 
176     /**
177      * TODO(b/279084380): Change callers to just call startActivityDismissingKeyguard and deprecate
178      *   this.
179      */
180     override fun startActivity(
181         intent: Intent,
182         onlyProvisioned: Boolean,
183         dismissShade: Boolean,
184         flags: Int,
185     ) {
186         activityStarterInternal.startActivityDismissingKeyguard(
187             intent = intent,
188             onlyProvisioned = onlyProvisioned,
189             dismissShade = dismissShade,
190             flags = flags,
191         )
192     }
193 
194     override fun startActivity(
195         intent: Intent,
196         dismissShade: Boolean,
197         animationController: ActivityLaunchAnimator.Controller?,
198         showOverLockscreenWhenLocked: Boolean,
199     ) {
200         activityStarterInternal.startActivity(
201             intent = intent,
202             dismissShade = dismissShade,
203             animationController = animationController,
204             showOverLockscreenWhenLocked = showOverLockscreenWhenLocked,
205         )
206     }
207     override fun startActivity(
208         intent: Intent,
209         dismissShade: Boolean,
210         animationController: ActivityLaunchAnimator.Controller?,
211         showOverLockscreenWhenLocked: Boolean,
212         userHandle: UserHandle?,
213     ) {
214         activityStarterInternal.startActivity(
215             intent = intent,
216             dismissShade = dismissShade,
217             animationController = animationController,
218             showOverLockscreenWhenLocked = showOverLockscreenWhenLocked,
219             userHandle = userHandle,
220         )
221     }
222 
223     override fun postStartActivityDismissingKeyguard(intent: PendingIntent) {
224         postOnUiThread {
225             activityStarterInternal.startPendingIntentDismissingKeyguard(
226                 intent = intent,
227             )
228         }
229     }
230 
231     override fun postStartActivityDismissingKeyguard(
232         intent: PendingIntent,
233         animationController: ActivityLaunchAnimator.Controller?
234     ) {
235         postOnUiThread {
236             activityStarterInternal.startPendingIntentDismissingKeyguard(
237                 intent = intent,
238                 animationController = animationController,
239             )
240         }
241     }
242 
243     override fun postStartActivityDismissingKeyguard(intent: Intent, delay: Int) {
244         postOnUiThread(delay) {
245             activityStarterInternal.startActivityDismissingKeyguard(
246                 intent = intent,
247                 onlyProvisioned = true,
248                 dismissShade = true,
249             )
250         }
251     }
252 
253     override fun postStartActivityDismissingKeyguard(
254         intent: Intent,
255         delay: Int,
256         animationController: ActivityLaunchAnimator.Controller?,
257     ) {
258         postOnUiThread(delay) {
259             activityStarterInternal.startActivityDismissingKeyguard(
260                 intent = intent,
261                 onlyProvisioned = true,
262                 dismissShade = true,
263                 animationController = animationController,
264             )
265         }
266     }
267 
268     override fun postStartActivityDismissingKeyguard(
269         intent: Intent,
270         delay: Int,
271         animationController: ActivityLaunchAnimator.Controller?,
272         customMessage: String?,
273     ) {
274         postOnUiThread(delay) {
275             activityStarterInternal.startActivityDismissingKeyguard(
276                 intent = intent,
277                 onlyProvisioned = true,
278                 dismissShade = true,
279                 animationController = animationController,
280                 customMessage = customMessage,
281             )
282         }
283     }
284 
285     override fun dismissKeyguardThenExecute(
286         action: OnDismissAction,
287         cancel: Runnable?,
288         afterKeyguardGone: Boolean,
289     ) {
290         activityStarterInternal.dismissKeyguardThenExecute(
291             action = action,
292             cancel = cancel,
293             afterKeyguardGone = afterKeyguardGone,
294         )
295     }
296 
297     override fun dismissKeyguardThenExecute(
298         action: OnDismissAction,
299         cancel: Runnable?,
300         afterKeyguardGone: Boolean,
301         customMessage: String?,
302     ) {
303         activityStarterInternal.dismissKeyguardThenExecute(
304             action = action,
305             cancel = cancel,
306             afterKeyguardGone = afterKeyguardGone,
307             customMessage = customMessage,
308         )
309     }
310 
311     override fun startActivityDismissingKeyguard(
312         intent: Intent,
313         onlyProvisioned: Boolean,
314         dismissShade: Boolean,
315     ) {
316         activityStarterInternal.startActivityDismissingKeyguard(
317             intent = intent,
318             onlyProvisioned = onlyProvisioned,
319             dismissShade = dismissShade,
320         )
321     }
322 
323     override fun startActivityDismissingKeyguard(
324         intent: Intent,
325         onlyProvisioned: Boolean,
326         dismissShade: Boolean,
327         disallowEnterPictureInPictureWhileLaunching: Boolean,
328         callback: ActivityStarter.Callback?,
329         flags: Int,
330         animationController: ActivityLaunchAnimator.Controller?,
331         userHandle: UserHandle?,
332     ) {
333         activityStarterInternal.startActivityDismissingKeyguard(
334             intent = intent,
335             onlyProvisioned = onlyProvisioned,
336             dismissShade = dismissShade,
337             disallowEnterPictureInPictureWhileLaunching =
338                 disallowEnterPictureInPictureWhileLaunching,
339             callback = callback,
340             flags = flags,
341             animationController = animationController,
342             userHandle = userHandle,
343         )
344     }
345 
346     override fun executeRunnableDismissingKeyguard(
347         runnable: Runnable?,
348         cancelAction: Runnable?,
349         dismissShade: Boolean,
350         afterKeyguardGone: Boolean,
351         deferred: Boolean,
352     ) {
353         activityStarterInternal.executeRunnableDismissingKeyguard(
354             runnable = runnable,
355             cancelAction = cancelAction,
356             dismissShade = dismissShade,
357             afterKeyguardGone = afterKeyguardGone,
358             deferred = deferred,
359         )
360     }
361 
362     override fun executeRunnableDismissingKeyguard(
363         runnable: Runnable?,
364         cancelAction: Runnable?,
365         dismissShade: Boolean,
366         afterKeyguardGone: Boolean,
367         deferred: Boolean,
368         willAnimateOnKeyguard: Boolean,
369         customMessage: String?,
370     ) {
371         activityStarterInternal.executeRunnableDismissingKeyguard(
372             runnable = runnable,
373             cancelAction = cancelAction,
374             dismissShade = dismissShade,
375             afterKeyguardGone = afterKeyguardGone,
376             deferred = deferred,
377             willAnimateOnKeyguard = willAnimateOnKeyguard,
378             customMessage = customMessage,
379         )
380     }
381 
382     override fun postQSRunnableDismissingKeyguard(runnable: Runnable?) {
383         postOnUiThread {
384             statusBarStateController.setLeaveOpenOnKeyguardHide(true)
385             activityStarterInternal.executeRunnableDismissingKeyguard(
386                 runnable = { runnable?.let { postOnUiThread(runnable = it) } },
387             )
388         }
389     }
390 
391     private fun postOnUiThread(delay: Int = 0, runnable: Runnable) {
392         mainExecutor.executeDelayed(runnable, delay.toLong())
393     }
394 
395     /**
396      * Whether we should animate an activity launch.
397      *
398      * Note: This method must be called *before* dismissing the keyguard.
399      */
400     private fun shouldAnimateLaunch(
401         isActivityIntent: Boolean,
402         showOverLockscreen: Boolean,
403     ): Boolean {
404         // TODO(b/294418322): Support launch animations when occluded.
405         if (keyguardStateController.isOccluded) {
406             return false
407         }
408 
409         // Always animate if we are not showing the keyguard or if we animate over the lockscreen
410         // (without unlocking it).
411         if (showOverLockscreen || !keyguardStateController.isShowing) {
412             return true
413         }
414 
415         // We don't animate non-activity launches as they can break the animation.
416         // TODO(b/184121838): Support non activity launches on the lockscreen.
417         return isActivityIntent
418     }
419 
420     override fun shouldAnimateLaunch(isActivityIntent: Boolean): Boolean {
421         return shouldAnimateLaunch(isActivityIntent, false)
422     }
423 
424     /**
425      * Encapsulates the activity logic for activity starter.
426      *
427      * Logic is duplicated in {@link CentralSurfacesImpl}
428      */
429     private inner class ActivityStarterInternal {
430         /** Starts an activity after dismissing keyguard. */
431         fun startActivityDismissingKeyguard(
432             intent: Intent,
433             onlyProvisioned: Boolean = false,
434             dismissShade: Boolean = false,
435             disallowEnterPictureInPictureWhileLaunching: Boolean = false,
436             callback: ActivityStarter.Callback? = null,
437             flags: Int = 0,
438             animationController: ActivityLaunchAnimator.Controller? = null,
439             userHandle: UserHandle? = null,
440             customMessage: String? = null,
441         ) {
442             val userHandle: UserHandle = userHandle ?: getActivityUserHandle(intent)
443 
444             if (onlyProvisioned && !deviceProvisionedController.isDeviceProvisioned) return
445 
446             val willLaunchResolverActivity: Boolean =
447                 activityIntentHelper.wouldLaunchResolverActivity(
448                     intent,
449                     lockScreenUserManager.currentUserId
450                 )
451 
452             val animate =
453                 animationController != null &&
454                     !willLaunchResolverActivity &&
455                     shouldAnimateLaunch(isActivityIntent = true)
456             val animController =
457                 wrapAnimationController(
458                     animationController = animationController,
459                     dismissShade = dismissShade,
460                     isLaunchForActivity = true,
461                 )
462 
463             // If we animate, we will dismiss the shade only once the animation is done. This is
464             // taken care of by the StatusBarLaunchAnimationController.
465             val dismissShadeDirectly = dismissShade && animController == null
466 
467             val runnable = Runnable {
468                 assistManagerLazy.get().hideAssist()
469                 intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
470                 intent.addFlags(flags)
471                 val result = intArrayOf(ActivityManager.START_CANCELED)
472                 activityLaunchAnimator.startIntentWithAnimation(
473                     animController,
474                     animate,
475                     intent.getPackage()
476                 ) { adapter: RemoteAnimationAdapter? ->
477                     val options =
478                         ActivityOptions(CentralSurfaces.getActivityOptions(displayId, adapter))
479 
480                     // We know that the intent of the caller is to dismiss the keyguard and
481                     // this runnable is called right after the keyguard is solved, so we tell
482                     // WM that we should dismiss it to avoid flickers when opening an activity
483                     // that can also be shown over the keyguard.
484                     options.setDismissKeyguard()
485                     options.setDisallowEnterPictureInPictureWhileLaunching(
486                         disallowEnterPictureInPictureWhileLaunching
487                     )
488                     if (isInsecureCameraIntent(intent)) {
489                         // Normally an activity will set it's requested rotation
490                         // animation on its window. However when launching an activity
491                         // causes the orientation to change this is too late. In these cases
492                         // the default animation is used. This doesn't look good for
493                         // the camera (as it rotates the camera contents out of sync
494                         // with physical reality). So, we ask the WindowManager to
495                         // force the cross fade animation if an orientation change
496                         // happens to occur during the launch.
497                         options.rotationAnimationHint =
498                             WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
499                     }
500                     if (Settings.Panel.ACTION_VOLUME == intent.action) {
501                         // Settings Panel is implemented as activity(not a dialog), so
502                         // underlying app is paused and may enter picture-in-picture mode
503                         // as a result.
504                         // So we need to disable picture-in-picture mode here
505                         // if it is volume panel.
506                         options.setDisallowEnterPictureInPictureWhileLaunching(true)
507                     }
508                     try {
509                         result[0] =
510                             ActivityTaskManager.getService()
511                                 .startActivityAsUser(
512                                     null,
513                                     context.basePackageName,
514                                     context.attributionTag,
515                                     intent,
516                                     intent.resolveTypeIfNeeded(context.contentResolver),
517                                     null,
518                                     null,
519                                     0,
520                                     Intent.FLAG_ACTIVITY_NEW_TASK,
521                                     null,
522                                     options.toBundle(),
523                                     userHandle.identifier,
524                                 )
525                     } catch (e: RemoteException) {
526                         Log.w(TAG, "Unable to start activity", e)
527                     }
528                     result[0]
529                 }
530                 callback?.onActivityStarted(result[0])
531             }
532             val cancelRunnable = Runnable {
533                 callback?.onActivityStarted(ActivityManager.START_CANCELED)
534             }
535             // Do not deferKeyguard when occluded because, when keyguard is occluded,
536             // we do not launch the activity until keyguard is done.
537             val occluded = (keyguardStateController.isShowing && keyguardStateController.isOccluded)
538             val deferred = !occluded
539             executeRunnableDismissingKeyguard(
540                 runnable,
541                 cancelRunnable,
542                 dismissShadeDirectly,
543                 willLaunchResolverActivity,
544                 deferred,
545                 animate,
546                 customMessage,
547             )
548         }
549 
550         /** Starts a pending intent after dismissing keyguard. */
551         fun startPendingIntentDismissingKeyguard(
552             intent: PendingIntent,
553             intentSentUiThreadCallback: Runnable? = null,
554             associatedView: View? = null,
555             animationController: ActivityLaunchAnimator.Controller? = null,
556         ) {
557             val animationController =
558                 if (associatedView is ExpandableNotificationRow) {
559                     centralSurfaces?.getAnimatorControllerFromNotification(associatedView)
560                 } else animationController
561 
562             val willLaunchResolverActivity =
563                 (intent.isActivity &&
564                     activityIntentHelper.wouldPendingLaunchResolverActivity(
565                         intent,
566                         lockScreenUserManager.currentUserId,
567                     ))
568 
569             val animate =
570                 !willLaunchResolverActivity &&
571                     animationController != null &&
572                     shouldAnimateLaunch(intent.isActivity)
573 
574             // If we animate, don't collapse the shade and defer the keyguard dismiss (in case we
575             // run the animation on the keyguard). The animation will take care of (instantly)
576             // collapsing the shade and hiding the keyguard once it is done.
577             val collapse = !animate
578             executeRunnableDismissingKeyguard(
579                 runnable = {
580                     try {
581                         // We wrap animationCallback with a StatusBarLaunchAnimatorController so
582                         // that the shade is collapsed after the animation (or when it is cancelled,
583                         // aborted, etc).
584                         val controller: ActivityLaunchAnimator.Controller? =
585                             wrapAnimationController(
586                                 animationController = animationController,
587                                 dismissShade = true,
588                                 isLaunchForActivity = intent.isActivity,
589                             )
590                         activityLaunchAnimator.startPendingIntentWithAnimation(
591                             controller,
592                             animate,
593                             intent.creatorPackage,
594                             object : PendingIntentStarter {
595                                 override fun startPendingIntent(
596                                     animationAdapter: RemoteAnimationAdapter?
597                                 ): Int {
598                                     val options =
599                                         ActivityOptions(
600                                             CentralSurfaces.getActivityOptions(
601                                                 displayId,
602                                                 animationAdapter
603                                             )
604                                         )
605                                     // TODO b/221255671: restrict this to only be set for
606                                     // notifications
607                                     options.isEligibleForLegacyPermissionPrompt = true
608                                     options.setPendingIntentBackgroundActivityStartMode(
609                                         ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
610                                     )
611                                     return intent.sendAndReturnResult(
612                                         null,
613                                         0,
614                                         null,
615                                         null,
616                                         null,
617                                         null,
618                                         options.toBundle()
619                                     )
620                                 }
621                             },
622                         )
623                     } catch (e: PendingIntent.CanceledException) {
624                         // the stack trace isn't very helpful here.
625                         // Just log the exception message.
626                         Log.w(TAG, "Sending intent failed: $e")
627                         if (!collapse) {
628                             // executeRunnableDismissingKeyguard did not collapse for us already.
629                             shadeControllerLazy.get().collapseOnMainThread()
630                         }
631                         // TODO: Dismiss Keyguard.
632                     }
633                     if (intent.isActivity) {
634                         assistManagerLazy.get().hideAssist()
635                     }
636                     intentSentUiThreadCallback?.let { postOnUiThread(runnable = it) }
637                 },
638                 afterKeyguardGone = willLaunchResolverActivity,
639                 dismissShade = collapse,
640                 willAnimateOnKeyguard = animate,
641             )
642         }
643 
644         /** Starts an Activity. */
645         fun startActivity(
646             intent: Intent,
647             dismissShade: Boolean = false,
648             animationController: ActivityLaunchAnimator.Controller? = null,
649             showOverLockscreenWhenLocked: Boolean = false,
650             userHandle: UserHandle? = null,
651         ) {
652             val userHandle = userHandle ?: getActivityUserHandle(intent)
653             // Make sure that we dismiss the keyguard if it is directly dismissible or when we don't
654             // want to show the activity above it.
655             if (keyguardStateController.isUnlocked || !showOverLockscreenWhenLocked) {
656                 startActivityDismissingKeyguard(
657                     intent = intent,
658                     onlyProvisioned = false,
659                     dismissShade = dismissShade,
660                     disallowEnterPictureInPictureWhileLaunching = false,
661                     callback = null,
662                     flags = 0,
663                     animationController = animationController,
664                     userHandle = userHandle,
665                 )
666                 return
667             }
668 
669             val animate =
670                 animationController != null &&
671                     shouldAnimateLaunch(
672                         /* isActivityIntent= */ true,
673                         showOverLockscreenWhenLocked
674                     ) == true
675 
676             var controller: ActivityLaunchAnimator.Controller? = null
677             if (animate) {
678                 // Wrap the animation controller to dismiss the shade and set
679                 // mIsLaunchingActivityOverLockscreen during the animation.
680                 val delegate =
681                     wrapAnimationController(
682                         animationController = animationController,
683                         dismissShade = dismissShade,
684                         isLaunchForActivity = true,
685                     )
686                 delegate?.let {
687                     controller =
688                         object : DelegateLaunchAnimatorController(delegate) {
689                             override fun onIntentStarted(willAnimate: Boolean) {
690                                 delegate?.onIntentStarted(willAnimate)
691                                 if (willAnimate) {
692                                     centralSurfaces?.setIsLaunchingActivityOverLockscreen(true)
693                                 }
694                             }
695 
696                             override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
697                                 super.onLaunchAnimationStart(isExpandingFullyAbove)
698 
699                                 // Double check that the keyguard is still showing and not going
700                                 // away, but if so set the keyguard occluded. Typically, WM will let
701                                 // KeyguardViewMediator know directly, but we're overriding that to
702                                 // play the custom launch animation, so we need to take care of that
703                                 // here. The unocclude animation is not overridden, so WM will call
704                                 // KeyguardViewMediator's unocclude animation runner when the
705                                 // activity is exited.
706                                 if (
707                                     keyguardStateController.isShowing &&
708                                         !keyguardStateController.isKeyguardGoingAway
709                                 ) {
710                                     Log.d(TAG, "Setting occluded = true in #startActivity.")
711                                     keyguardViewMediatorLazy
712                                         .get()
713                                         .setOccluded(true /* isOccluded */, true /* animate */)
714                                 }
715                             }
716 
717                             override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
718                                 // Set mIsLaunchingActivityOverLockscreen to false before actually
719                                 // finishing the animation so that we can assume that
720                                 // mIsLaunchingActivityOverLockscreen being true means that we will
721                                 // collapse the shade (or at least run the post collapse runnables)
722                                 // later on.
723                                 centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
724                                 delegate?.onLaunchAnimationEnd(isExpandingFullyAbove)
725                             }
726 
727                             override fun onLaunchAnimationCancelled(
728                                 newKeyguardOccludedState: Boolean?
729                             ) {
730                                 if (newKeyguardOccludedState != null) {
731                                     keyguardViewMediatorLazy
732                                         .get()
733                                         .setOccluded(newKeyguardOccludedState, false /* animate */)
734                                 }
735 
736                                 // Set mIsLaunchingActivityOverLockscreen to false before actually
737                                 // finishing the animation so that we can assume that
738                                 // mIsLaunchingActivityOverLockscreen being true means that we will
739                                 // collapse the shade (or at least run the // post collapse
740                                 // runnables) later on.
741                                 centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
742                                 delegate.onLaunchAnimationCancelled(newKeyguardOccludedState)
743                             }
744                         }
745                 }
746             } else if (dismissShade) {
747                 // The animation will take care of dismissing the shade at the end of the animation.
748                 // If we don't animate, collapse it directly.
749                 shadeControllerLazy.get().cancelExpansionAndCollapseShade()
750             }
751 
752             // We should exit the dream to prevent the activity from starting below the
753             // dream.
754             if (keyguardUpdateMonitor.isDreaming) {
755                 centralSurfaces?.awakenDreams()
756             }
757 
758             activityLaunchAnimator.startIntentWithAnimation(
759                 controller,
760                 animate,
761                 intent.getPackage(),
762                 showOverLockscreenWhenLocked
763             ) { adapter: RemoteAnimationAdapter? ->
764                 TaskStackBuilder.create(context)
765                     .addNextIntent(intent)
766                     .startActivities(
767                         CentralSurfaces.getActivityOptions(displayId, adapter),
768                         userHandle
769                     )
770             }
771         }
772 
773         /** Executes an action after dismissing keyguard. */
774         fun dismissKeyguardThenExecute(
775             action: OnDismissAction,
776             cancel: Runnable? = null,
777             afterKeyguardGone: Boolean = false,
778             customMessage: String? = null,
779         ) {
780             if (
781                 !action.willRunAnimationOnKeyguard() &&
782                     wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP &&
783                     keyguardStateController.canDismissLockScreen() &&
784                     !statusBarStateController.leaveOpenOnKeyguardHide() &&
785                     dozeServiceHostLazy.get().isPulsing
786             ) {
787                 // Reuse the biometric wake-and-unlock transition if we dismiss keyguard from a
788                 // pulse.
789                 // TODO: Factor this transition out of BiometricUnlockController.
790                 biometricUnlockControllerLazy
791                     .get()
792                     .startWakeAndUnlock(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
793             }
794             if (keyguardStateController.isShowing) {
795                 statusBarKeyguardViewManagerLazy
796                     .get()
797                     .dismissWithAction(action, cancel, afterKeyguardGone, customMessage)
798             } else {
799                 // If the keyguard isn't showing but the device is dreaming, we should exit the
800                 // dream.
801                 if (keyguardUpdateMonitor.isDreaming) {
802                     centralSurfaces?.awakenDreams()
803                 }
804                 action.onDismiss()
805             }
806         }
807 
808         /** Executes an action after dismissing keyguard. */
809         fun executeRunnableDismissingKeyguard(
810             runnable: Runnable? = null,
811             cancelAction: Runnable? = null,
812             dismissShade: Boolean = false,
813             afterKeyguardGone: Boolean = false,
814             deferred: Boolean = false,
815             willAnimateOnKeyguard: Boolean = false,
816             customMessage: String? = null,
817         ) {
818             val onDismissAction: OnDismissAction =
819                 object : OnDismissAction {
820                     override fun onDismiss(): Boolean {
821                         if (runnable != null) {
822                             if (
823                                 keyguardStateController.isShowing &&
824                                     keyguardStateController.isOccluded
825                             ) {
826                                 statusBarKeyguardViewManagerLazy
827                                     .get()
828                                     .addAfterKeyguardGoneRunnable(runnable)
829                             } else {
830                                 mainExecutor.execute(runnable)
831                             }
832                         }
833                         if (dismissShade) {
834                             if (
835                                 shadeControllerLazy.get().isExpandedVisible &&
836                                     !statusBarKeyguardViewManagerLazy.get().isBouncerShowing
837                             ) {
838                                 shadeControllerLazy.get().animateCollapseShadeForcedDelayed()
839                             } else {
840                                 // Do it after DismissAction has been processed to conserve the
841                                 // needed ordering.
842                                 postOnUiThread {
843                                     shadeControllerLazy.get().runPostCollapseRunnables()
844                                 }
845                             }
846                         }
847                         return deferred
848                     }
849 
850                     override fun willRunAnimationOnKeyguard(): Boolean {
851                         return willAnimateOnKeyguard
852                     }
853                 }
854             dismissKeyguardThenExecute(
855                 onDismissAction,
856                 cancelAction,
857                 afterKeyguardGone,
858                 customMessage,
859             )
860         }
861 
862         /**
863          * Return a [ActivityLaunchAnimator.Controller] wrapping `animationController` so that:
864          * - if it launches in the notification shade window and `dismissShade` is true, then the
865          *   shade will be instantly dismissed at the end of the animation.
866          * - if it launches in status bar window, it will make the status bar window match the
867          *   device size during the animation (that way, the animation won't be clipped by the
868          *   status bar size).
869          *
870          * @param animationController the controller that is wrapped and will drive the main
871          *   animation.
872          * @param dismissShade whether the notification shade will be dismissed at the end of the
873          *   animation. This is ignored if `animationController` is not animating in the shade
874          *   window.
875          * @param isLaunchForActivity whether the launch is for an activity.
876          */
877         private fun wrapAnimationController(
878             animationController: ActivityLaunchAnimator.Controller?,
879             dismissShade: Boolean,
880             isLaunchForActivity: Boolean,
881         ): ActivityLaunchAnimator.Controller? {
882             if (animationController == null) {
883                 return null
884             }
885             val rootView = animationController.launchContainer.rootView
886             val controllerFromStatusBar: Optional<ActivityLaunchAnimator.Controller> =
887                 statusBarWindowController.wrapAnimationControllerIfInStatusBar(
888                     rootView,
889                     animationController
890                 )
891             if (controllerFromStatusBar.isPresent) {
892                 return controllerFromStatusBar.get()
893             }
894 
895             centralSurfaces?.let {
896                 // If the view is not in the status bar, then we are animating a view in the shade.
897                 // We have to make sure that we collapse it when the animation ends or is cancelled.
898                 if (dismissShade) {
899                     return StatusBarLaunchAnimatorController(
900                         animationController,
901                         shadeViewControllerLazy.get(),
902                         shadeControllerLazy.get(),
903                         notifShadeWindowControllerLazy.get(),
904                         isLaunchForActivity
905                     )
906                 }
907             }
908 
909             return animationController
910         }
911 
912         /** Retrieves the current user handle to start the Activity. */
913         private fun getActivityUserHandle(intent: Intent): UserHandle {
914             val packages: Array<String> =
915                 context.resources.getStringArray(R.array.system_ui_packages)
916             for (pkg in packages) {
917                 val componentName = intent.component ?: break
918                 if (pkg == componentName.packageName) {
919                     return UserHandle(UserHandle.myUserId())
920                 }
921             }
922             return userTracker.userHandle
923         }
924     }
925 }
926