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