diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java index 214b146a879cb..33c7c117dc95a 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java @@ -119,6 +119,16 @@ public interface ActivityStarter { boolean afterKeyguardGone, boolean deferred); + /** Execute a runnable after dismissing keyguard. */ + void executeRunnableDismissingKeyguard( + Runnable runnable, + Runnable cancelAction, + boolean dismissShade, + boolean afterKeyguardGone, + boolean deferred, + boolean willAnimateOnKeyguard, + @Nullable String customMessage); + interface Callback { void onActivityStarted(int resultCode); } diff --git a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java index 6b20dce63dfb0..bf84f8af02feb 100644 --- a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java @@ -35,6 +35,8 @@ import javax.inject.Inject; /** * Single common instance of ActivityStarter that can be gotten and referenced from anywhere, but * delegates to an actual implementation (CentralSurfaces). + * + * @deprecated Migrating to ActivityStarterImpl */ @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @SysUISingleton @@ -206,4 +208,14 @@ public class ActivityStarterDelegate implements ActivityStarter { starter -> starter.executeRunnableDismissingKeyguard(runnable, cancelAction, dismissShade, afterKeyguardGone, deferred)); } + + @Override + public void executeRunnableDismissingKeyguard(Runnable runnable, Runnable cancelAction, + boolean dismissShade, boolean afterKeyguardGone, boolean deferred, + boolean willAnimateOnKeyguard, @Nullable String customMessage) { + mActualStarterOptionalLazy.get().ifPresent( + starter -> starter.executeRunnableDismissingKeyguard(runnable, cancelAction, + dismissShade, afterKeyguardGone, deferred, willAnimateOnKeyguard, + customMessage)); + } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java b/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java index 67ad3db74f8ad..8764297c1b5dd 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java @@ -18,6 +18,8 @@ package com.android.systemui.dagger; import com.android.systemui.ActivityStarterDelegate; import com.android.systemui.classifier.FalsingManagerProxy; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.globalactions.GlobalActionsComponent; import com.android.systemui.globalactions.GlobalActionsImpl; import com.android.systemui.plugins.ActivityStarter; @@ -28,6 +30,7 @@ import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarStateControllerImpl; +import com.android.systemui.statusbar.phone.ActivityStarterImpl; import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl; import com.android.systemui.volume.VolumeDialogControllerImpl; @@ -46,7 +49,11 @@ public abstract class PluginModule { /** */ @Provides static ActivityStarter provideActivityStarter(ActivityStarterDelegate delegate, - PluginDependencyProvider dependencyProvider) { + PluginDependencyProvider dependencyProvider, ActivityStarterImpl activityStarterImpl, + FeatureFlags featureFlags) { + if (featureFlags.isEnabled(Flags.USE_NEW_ACTIVITY_STARTER)) { + return activityStarterImpl; + } dependencyProvider.allowPluginDependency(ActivityStarter.class, delegate); return delegate; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt new file mode 100644 index 0000000000000..d9dc8878fa61d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt @@ -0,0 +1,867 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.statusbar.phone + +import android.app.ActivityManager +import android.app.ActivityOptions +import android.app.ActivityTaskManager +import android.app.PendingIntent +import android.app.TaskStackBuilder +import android.content.Context +import android.content.Intent +import android.os.RemoteException +import android.os.UserHandle +import android.provider.Settings +import android.util.Log +import android.view.RemoteAnimationAdapter +import android.view.View +import android.view.WindowManager +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.ActivityIntentHelper +import com.android.systemui.R +import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityLaunchAnimator.PendingIntentStarter +import com.android.systemui.animation.DelegateLaunchAnimatorController +import com.android.systemui.assist.AssistManager +import com.android.systemui.camera.CameraIntents.Companion.isInsecureCameraIntent +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.KeyguardViewMediator +import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.ActivityStarter.OnDismissAction +import com.android.systemui.settings.UserTracker +import com.android.systemui.shade.ShadeController +import com.android.systemui.statusbar.NotificationLockscreenUserManager +import com.android.systemui.statusbar.SysuiStatusBarStateController +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.kotlin.getOrNull +import dagger.Lazy +import java.util.Optional +import javax.inject.Inject + +/** Handles start activity logic in SystemUI. */ +@SysUISingleton +class ActivityStarterImpl +@Inject +constructor( + private val centralSurfacesOptLazy: Lazy>, + private val assistManagerLazy: Lazy, + private val dozeServiceHostLazy: Lazy, + private val biometricUnlockControllerLazy: Lazy, + private val keyguardViewMediatorLazy: Lazy, + private val shadeControllerLazy: Lazy, + private val statusBarKeyguardViewManagerLazy: Lazy, + private val activityLaunchAnimator: ActivityLaunchAnimator, + private val context: Context, + private val lockScreenUserManager: NotificationLockscreenUserManager, + private val statusBarWindowController: StatusBarWindowController, + private val wakefulnessLifecycle: WakefulnessLifecycle, + private val keyguardStateController: KeyguardStateController, + private val statusBarStateController: SysuiStatusBarStateController, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val deviceProvisionedController: DeviceProvisionedController, + private val userTracker: UserTracker, + private val activityIntentHelper: ActivityIntentHelper, + @Main private val mainExecutor: DelayableExecutor, +) : ActivityStarter { + companion object { + const val TAG = "ActivityStarterImpl" + } + + private val centralSurfaces: CentralSurfaces? + get() = centralSurfacesOptLazy.get().getOrNull() + + private val activityStarterInternal = ActivityStarterInternal() + + override fun startPendingIntentDismissingKeyguard(intent: PendingIntent) { + activityStarterInternal.startPendingIntentDismissingKeyguard(intent = intent) + } + + override fun startPendingIntentDismissingKeyguard( + intent: PendingIntent, + intentSentUiThreadCallback: Runnable?, + ) { + activityStarterInternal.startPendingIntentDismissingKeyguard( + intent = intent, + intentSentUiThreadCallback = intentSentUiThreadCallback, + ) + } + + override fun startPendingIntentDismissingKeyguard( + intent: PendingIntent, + intentSentUiThreadCallback: Runnable?, + associatedView: View?, + ) { + activityStarterInternal.startPendingIntentDismissingKeyguard( + intent = intent, + intentSentUiThreadCallback = intentSentUiThreadCallback, + associatedView = associatedView, + ) + } + + override fun startPendingIntentDismissingKeyguard( + intent: PendingIntent, + intentSentUiThreadCallback: Runnable?, + animationController: ActivityLaunchAnimator.Controller?, + ) { + activityStarterInternal.startPendingIntentDismissingKeyguard( + intent = intent, + intentSentUiThreadCallback = intentSentUiThreadCallback, + animationController = animationController, + ) + } + + /** + * TODO(b/279084380): Change callers to just call startActivityDismissingKeyguard and deprecate + * this. + */ + override fun startActivity(intent: Intent, dismissShade: Boolean) { + activityStarterInternal.startActivityDismissingKeyguard( + intent = intent, + dismissShade = dismissShade, + ) + } + + /** + * TODO(b/279084380): Change callers to just call startActivityDismissingKeyguard and deprecate + * this. + */ + override fun startActivity(intent: Intent, onlyProvisioned: Boolean, dismissShade: Boolean) { + activityStarterInternal.startActivityDismissingKeyguard( + intent = intent, + onlyProvisioned = onlyProvisioned, + dismissShade = dismissShade, + ) + } + + /** + * TODO(b/279084380): Change callers to just call startActivityDismissingKeyguard and deprecate + * this. + */ + override fun startActivity( + intent: Intent, + dismissShade: Boolean, + callback: ActivityStarter.Callback?, + ) { + activityStarterInternal.startActivityDismissingKeyguard( + intent = intent, + dismissShade = dismissShade, + callback = callback, + ) + } + + /** + * TODO(b/279084380): Change callers to just call startActivityDismissingKeyguard and deprecate + * this. + */ + override fun startActivity( + intent: Intent, + onlyProvisioned: Boolean, + dismissShade: Boolean, + flags: Int, + ) { + activityStarterInternal.startActivityDismissingKeyguard( + intent = intent, + onlyProvisioned = onlyProvisioned, + dismissShade = dismissShade, + flags = flags, + ) + } + + override fun startActivity( + intent: Intent, + dismissShade: Boolean, + animationController: ActivityLaunchAnimator.Controller?, + showOverLockscreenWhenLocked: Boolean, + ) { + activityStarterInternal.startActivity( + intent = intent, + dismissShade = dismissShade, + animationController = animationController, + showOverLockscreenWhenLocked = showOverLockscreenWhenLocked, + ) + } + override fun startActivity( + intent: Intent, + dismissShade: Boolean, + animationController: ActivityLaunchAnimator.Controller?, + showOverLockscreenWhenLocked: Boolean, + userHandle: UserHandle?, + ) { + activityStarterInternal.startActivity( + intent = intent, + dismissShade = dismissShade, + animationController = animationController, + showOverLockscreenWhenLocked = showOverLockscreenWhenLocked, + userHandle = userHandle, + ) + } + + override fun postStartActivityDismissingKeyguard(intent: PendingIntent) { + postOnUiThread { + activityStarterInternal.startPendingIntentDismissingKeyguard( + intent = intent, + ) + } + } + + override fun postStartActivityDismissingKeyguard( + intent: PendingIntent, + animationController: ActivityLaunchAnimator.Controller? + ) { + postOnUiThread { + activityStarterInternal.startPendingIntentDismissingKeyguard( + intent = intent, + animationController = animationController, + ) + } + } + + override fun postStartActivityDismissingKeyguard(intent: Intent, delay: Int) { + postOnUiThread(delay) { + activityStarterInternal.startActivityDismissingKeyguard(intent = intent) + } + } + + override fun postStartActivityDismissingKeyguard( + intent: Intent, + delay: Int, + animationController: ActivityLaunchAnimator.Controller?, + ) { + postOnUiThread(delay) { + activityStarterInternal.startActivityDismissingKeyguard( + intent = intent, + animationController = animationController, + ) + } + } + + override fun postStartActivityDismissingKeyguard( + intent: Intent, + delay: Int, + animationController: ActivityLaunchAnimator.Controller?, + customMessage: String?, + ) { + postOnUiThread(delay) { + activityStarterInternal.startActivityDismissingKeyguard( + intent = intent, + animationController = animationController, + customMessage = customMessage, + ) + } + } + + override fun dismissKeyguardThenExecute( + action: OnDismissAction, + cancel: Runnable?, + afterKeyguardGone: Boolean, + ) { + activityStarterInternal.dismissKeyguardThenExecute( + action = action, + cancel = cancel, + afterKeyguardGone = afterKeyguardGone, + ) + } + + override fun dismissKeyguardThenExecute( + action: OnDismissAction, + cancel: Runnable?, + afterKeyguardGone: Boolean, + customMessage: String?, + ) { + activityStarterInternal.dismissKeyguardThenExecute( + action = action, + cancel = cancel, + afterKeyguardGone = afterKeyguardGone, + customMessage = customMessage, + ) + } + + override fun startActivityDismissingKeyguard( + intent: Intent, + onlyProvisioned: Boolean, + dismissShade: Boolean, + disallowEnterPictureInPictureWhileLaunching: Boolean, + callback: ActivityStarter.Callback?, + flags: Int, + animationController: ActivityLaunchAnimator.Controller?, + userHandle: UserHandle?, + ) { + activityStarterInternal.startActivityDismissingKeyguard( + intent = intent, + onlyProvisioned = onlyProvisioned, + dismissShade = dismissShade, + disallowEnterPictureInPictureWhileLaunching = + disallowEnterPictureInPictureWhileLaunching, + callback = callback, + flags = flags, + animationController = animationController, + userHandle = userHandle, + ) + } + + override fun executeRunnableDismissingKeyguard( + runnable: Runnable?, + cancelAction: Runnable?, + dismissShade: Boolean, + afterKeyguardGone: Boolean, + deferred: Boolean, + ) { + activityStarterInternal.executeRunnableDismissingKeyguard( + runnable = runnable, + cancelAction = cancelAction, + dismissShade = dismissShade, + afterKeyguardGone = afterKeyguardGone, + deferred = deferred, + ) + } + + override fun executeRunnableDismissingKeyguard( + runnable: Runnable?, + cancelAction: Runnable?, + dismissShade: Boolean, + afterKeyguardGone: Boolean, + deferred: Boolean, + willAnimateOnKeyguard: Boolean, + customMessage: String?, + ) { + activityStarterInternal.executeRunnableDismissingKeyguard( + runnable = runnable, + cancelAction = cancelAction, + dismissShade = dismissShade, + afterKeyguardGone = afterKeyguardGone, + deferred = deferred, + willAnimateOnKeyguard = willAnimateOnKeyguard, + customMessage = customMessage, + ) + } + + override fun postQSRunnableDismissingKeyguard(runnable: Runnable?) { + postOnUiThread { + statusBarStateController.setLeaveOpenOnKeyguardHide(true) + activityStarterInternal.executeRunnableDismissingKeyguard( + runnable = { runnable?.let { postOnUiThread(runnable = it) } }, + ) + } + } + + private fun postOnUiThread(delay: Int = 0, runnable: Runnable) { + mainExecutor.executeDelayed(runnable, delay.toLong()) + } + + /** + * Encapsulates the activity logic for activity starter. + * + * Logic is duplicated in {@link CentralSurfacesImpl} + */ + private inner class ActivityStarterInternal { + /** Starts an activity after dismissing keyguard. */ + fun startActivityDismissingKeyguard( + intent: Intent, + onlyProvisioned: Boolean = false, + dismissShade: Boolean = false, + disallowEnterPictureInPictureWhileLaunching: Boolean = false, + callback: ActivityStarter.Callback? = null, + flags: Int = 0, + animationController: ActivityLaunchAnimator.Controller? = null, + userHandle: UserHandle? = null, + customMessage: String? = null, + ) { + val userHandle: UserHandle = userHandle ?: getActivityUserHandle(intent) + + if (onlyProvisioned && !deviceProvisionedController.isDeviceProvisioned) return + + val willLaunchResolverActivity: Boolean = + activityIntentHelper.wouldLaunchResolverActivity( + intent, + lockScreenUserManager.currentUserId + ) + + val animate = + animationController != null && + !willLaunchResolverActivity && + centralSurfaces?.shouldAnimateLaunch(true /* isActivityIntent */) == true + val animController = + wrapAnimationController( + animationController = animationController, + dismissShade = dismissShade, + isLaunchForActivity = true, + ) + + // If we animate, we will dismiss the shade only once the animation is done. This is + // taken care of by the StatusBarLaunchAnimationController. + val dismissShadeDirectly = dismissShade && animController == null + + val runnable = Runnable { + assistManagerLazy.get().hideAssist() + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP + intent.addFlags(flags) + val result = intArrayOf(ActivityManager.START_CANCELED) + activityLaunchAnimator.startIntentWithAnimation( + animController, + animate, + intent.getPackage() + ) { adapter: RemoteAnimationAdapter? -> + val options = + ActivityOptions( + CentralSurfaces.getActivityOptions(centralSurfaces!!.displayId, adapter) + ) + + // We know that the intent of the caller is to dismiss the keyguard and + // this runnable is called right after the keyguard is solved, so we tell + // WM that we should dismiss it to avoid flickers when opening an activity + // that can also be shown over the keyguard. + options.setDismissKeyguard() + options.setDisallowEnterPictureInPictureWhileLaunching( + disallowEnterPictureInPictureWhileLaunching + ) + if (isInsecureCameraIntent(intent)) { + // Normally an activity will set it's requested rotation + // animation on its window. However when launching an activity + // causes the orientation to change this is too late. In these cases + // the default animation is used. This doesn't look good for + // the camera (as it rotates the camera contents out of sync + // with physical reality). So, we ask the WindowManager to + // force the cross fade animation if an orientation change + // happens to occur during the launch. + options.rotationAnimationHint = + WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS + } + if (Settings.Panel.ACTION_VOLUME == intent.action) { + // Settings Panel is implemented as activity(not a dialog), so + // underlying app is paused and may enter picture-in-picture mode + // as a result. + // So we need to disable picture-in-picture mode here + // if it is volume panel. + options.setDisallowEnterPictureInPictureWhileLaunching(true) + } + try { + result[0] = + ActivityTaskManager.getService() + .startActivityAsUser( + null, + context.basePackageName, + context.attributionTag, + intent, + intent.resolveTypeIfNeeded(context.contentResolver), + null, + null, + 0, + Intent.FLAG_ACTIVITY_NEW_TASK, + null, + options.toBundle(), + userHandle.identifier, + ) + } catch (e: RemoteException) { + Log.w(TAG, "Unable to start activity", e) + } + result[0] + } + callback?.onActivityStarted(result[0]) + } + val cancelRunnable = Runnable { + callback?.onActivityStarted(ActivityManager.START_CANCELED) + } + // Do not deferKeyguard when occluded because, when keyguard is occluded, + // we do not launch the activity until keyguard is done. + val occluded = (keyguardStateController.isShowing && keyguardStateController.isOccluded) + val deferred = !occluded + executeRunnableDismissingKeyguard( + runnable, + cancelRunnable, + dismissShadeDirectly, + willLaunchResolverActivity, + deferred, + animate, + customMessage, + ) + } + + /** Starts a pending intent after dismissing keyguard. */ + fun startPendingIntentDismissingKeyguard( + intent: PendingIntent, + intentSentUiThreadCallback: Runnable? = null, + associatedView: View? = null, + animationController: ActivityLaunchAnimator.Controller? = null, + ) { + val animationController = + if (associatedView is ExpandableNotificationRow) { + centralSurfaces?.getAnimatorControllerFromNotification(associatedView) + } else animationController + + val willLaunchResolverActivity = + (intent.isActivity && + activityIntentHelper.wouldPendingLaunchResolverActivity( + intent, + lockScreenUserManager.currentUserId, + )) + + val animate = + !willLaunchResolverActivity && + animationController != null && + centralSurfaces?.shouldAnimateLaunch(intent.isActivity) == true + + // If we animate, don't collapse the shade and defer the keyguard dismiss (in case we + // run the animation on the keyguard). The animation will take care of (instantly) + // collapsing the shade and hiding the keyguard once it is done. + val collapse = !animate + executeRunnableDismissingKeyguard( + runnable = { + try { + // We wrap animationCallback with a StatusBarLaunchAnimatorController so + // that the shade is collapsed after the animation (or when it is cancelled, + // aborted, etc). + val controller: ActivityLaunchAnimator.Controller? = + wrapAnimationController( + animationController = animationController, + dismissShade = true, + isLaunchForActivity = intent.isActivity, + ) + activityLaunchAnimator.startPendingIntentWithAnimation( + controller, + animate, + intent.creatorPackage, + object : PendingIntentStarter { + override fun startPendingIntent( + animationAdapter: RemoteAnimationAdapter? + ): Int { + val options = + ActivityOptions( + CentralSurfaces.getActivityOptions( + centralSurfaces!!.displayId, + animationAdapter + ) + ) + // TODO b/221255671: restrict this to only be set for + // notifications + options.isEligibleForLegacyPermissionPrompt = true + return intent.sendAndReturnResult( + null, + 0, + null, + null, + null, + null, + options.toBundle() + ) + } + }, + ) + } catch (e: PendingIntent.CanceledException) { + // the stack trace isn't very helpful here. + // Just log the exception message. + Log.w(TAG, "Sending intent failed: $e") + if (!collapse) { + // executeRunnableDismissingKeyguard did not collapse for us already. + centralSurfaces?.collapsePanelOnMainThread() + } + // TODO: Dismiss Keyguard. + } + if (intent.isActivity) { + assistManagerLazy.get().hideAssist() + } + intentSentUiThreadCallback?.let { postOnUiThread(runnable = it) } + }, + afterKeyguardGone = willLaunchResolverActivity, + dismissShade = collapse, + willAnimateOnKeyguard = animate, + ) + } + + /** Starts an Activity. */ + fun startActivity( + intent: Intent, + dismissShade: Boolean = false, + animationController: ActivityLaunchAnimator.Controller? = null, + showOverLockscreenWhenLocked: Boolean = false, + userHandle: UserHandle? = null, + ) { + val userHandle = userHandle ?: getActivityUserHandle(intent) + // Make sure that we dismiss the keyguard if it is directly dismissible or when we don't + // want to show the activity above it. + if (keyguardStateController.isUnlocked || !showOverLockscreenWhenLocked) { + startActivityDismissingKeyguard( + intent = intent, + onlyProvisioned = false, + dismissShade = dismissShade, + disallowEnterPictureInPictureWhileLaunching = false, + callback = null, + flags = 0, + animationController = animationController, + userHandle = userHandle, + ) + return + } + + val animate = + animationController != null && + centralSurfaces?.shouldAnimateLaunch( + /* isActivityIntent= */ true, + showOverLockscreenWhenLocked + ) == true + + var controller: ActivityLaunchAnimator.Controller? = null + if (animate) { + // Wrap the animation controller to dismiss the shade and set + // mIsLaunchingActivityOverLockscreen during the animation. + val delegate = + wrapAnimationController( + animationController = animationController, + dismissShade = dismissShade, + isLaunchForActivity = true, + ) + delegate?.let { + controller = + object : DelegateLaunchAnimatorController(delegate) { + override fun onIntentStarted(willAnimate: Boolean) { + delegate?.onIntentStarted(willAnimate) + if (willAnimate) { + centralSurfaces?.setIsLaunchingActivityOverLockscreen(true) + } + } + + override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) { + super.onLaunchAnimationStart(isExpandingFullyAbove) + + // Double check that the keyguard is still showing and not going + // away, but if so set the keyguard occluded. Typically, WM will let + // KeyguardViewMediator know directly, but we're overriding that to + // play the custom launch animation, so we need to take care of that + // here. The unocclude animation is not overridden, so WM will call + // KeyguardViewMediator's unocclude animation runner when the + // activity is exited. + if ( + keyguardStateController.isShowing && + !keyguardStateController.isKeyguardGoingAway + ) { + Log.d(TAG, "Setting occluded = true in #startActivity.") + keyguardViewMediatorLazy + .get() + .setOccluded(true /* isOccluded */, true /* animate */) + } + } + + override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { + // Set mIsLaunchingActivityOverLockscreen to false before actually + // finishing the animation so that we can assume that + // mIsLaunchingActivityOverLockscreen being true means that we will + // collapse the shade (or at least run the post collapse runnables) + // later on. + centralSurfaces?.setIsLaunchingActivityOverLockscreen(false) + delegate?.onLaunchAnimationEnd(isExpandingFullyAbove) + } + + override fun onLaunchAnimationCancelled( + newKeyguardOccludedState: Boolean? + ) { + if (newKeyguardOccludedState != null) { + keyguardViewMediatorLazy + .get() + .setOccluded(newKeyguardOccludedState, false /* animate */) + } + + // Set mIsLaunchingActivityOverLockscreen to false before actually + // finishing the animation so that we can assume that + // mIsLaunchingActivityOverLockscreen being true means that we will + // collapse the shade (or at least run the // post collapse + // runnables) later on. + centralSurfaces?.setIsLaunchingActivityOverLockscreen(false) + delegate.onLaunchAnimationCancelled(newKeyguardOccludedState) + } + } + } + } else if (dismissShade) { + // The animation will take care of dismissing the shade at the end of the animation. + // If we don't animate, collapse it directly. + centralSurfaces?.collapseShade() + } + + // We should exit the dream to prevent the activity from starting below the + // dream. + if (keyguardUpdateMonitor.isDreaming) { + centralSurfaces?.awakenDreams() + } + + activityLaunchAnimator.startIntentWithAnimation( + controller, + animate, + intent.getPackage(), + showOverLockscreenWhenLocked + ) { adapter: RemoteAnimationAdapter? -> + TaskStackBuilder.create(context) + .addNextIntent(intent) + .startActivities( + CentralSurfaces.getActivityOptions(centralSurfaces!!.displayId, adapter), + userHandle + ) + } + } + + /** Executes an action after dismissing keyguard. */ + fun dismissKeyguardThenExecute( + action: OnDismissAction, + cancel: Runnable? = null, + afterKeyguardGone: Boolean = false, + customMessage: String? = null, + ) { + if ( + !action.willRunAnimationOnKeyguard() && + wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP && + keyguardStateController.canDismissLockScreen() && + !statusBarStateController.leaveOpenOnKeyguardHide() && + dozeServiceHostLazy.get().isPulsing + ) { + // Reuse the biometric wake-and-unlock transition if we dismiss keyguard from a + // pulse. + // TODO: Factor this transition out of BiometricUnlockController. + biometricUnlockControllerLazy + .get() + .startWakeAndUnlock(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING) + } + if (keyguardStateController.isShowing) { + statusBarKeyguardViewManagerLazy + .get() + .dismissWithAction(action, cancel, afterKeyguardGone, customMessage) + } else { + // If the keyguard isn't showing but the device is dreaming, we should exit the + // dream. + if (keyguardUpdateMonitor.isDreaming) { + centralSurfaces?.awakenDreams() + } + action.onDismiss() + } + } + + /** Executes an action after dismissing keyguard. */ + fun executeRunnableDismissingKeyguard( + runnable: Runnable? = null, + cancelAction: Runnable? = null, + dismissShade: Boolean = false, + afterKeyguardGone: Boolean = false, + deferred: Boolean = false, + willAnimateOnKeyguard: Boolean = false, + customMessage: String? = null, + ) { + val onDismissAction: OnDismissAction = + object : OnDismissAction { + override fun onDismiss(): Boolean { + if (runnable != null) { + if ( + keyguardStateController.isShowing && + keyguardStateController.isOccluded + ) { + statusBarKeyguardViewManagerLazy + .get() + .addAfterKeyguardGoneRunnable(runnable) + } else { + mainExecutor.execute(runnable) + } + } + if (dismissShade) { + if ( + shadeControllerLazy.get().isExpandedVisible && + !statusBarKeyguardViewManagerLazy.get().isBouncerShowing + ) { + shadeControllerLazy.get().animateCollapseShadeDelayed() + } else { + // Do it after DismissAction has been processed to conserve the + // needed ordering. + postOnUiThread { + shadeControllerLazy.get().runPostCollapseRunnables() + } + } + } + return deferred + } + + override fun willRunAnimationOnKeyguard(): Boolean { + return willAnimateOnKeyguard + } + } + dismissKeyguardThenExecute( + onDismissAction, + cancelAction, + afterKeyguardGone, + customMessage, + ) + } + + /** + * Return a [ActivityLaunchAnimator.Controller] wrapping `animationController` so that: + * - if it launches in the notification shade window and `dismissShade` is true, then the + * shade will be instantly dismissed at the end of the animation. + * - if it launches in status bar window, it will make the status bar window match the + * device size during the animation (that way, the animation won't be clipped by the + * status bar size). + * + * @param animationController the controller that is wrapped and will drive the main + * animation. + * @param dismissShade whether the notification shade will be dismissed at the end of the + * animation. This is ignored if `animationController` is not animating in the shade + * window. + * @param isLaunchForActivity whether the launch is for an activity. + */ + private fun wrapAnimationController( + animationController: ActivityLaunchAnimator.Controller?, + dismissShade: Boolean, + isLaunchForActivity: Boolean, + ): ActivityLaunchAnimator.Controller? { + if (animationController == null) { + return null + } + val rootView = animationController.launchContainer.rootView + val controllerFromStatusBar: Optional = + statusBarWindowController.wrapAnimationControllerIfInStatusBar( + rootView, + animationController + ) + if (controllerFromStatusBar.isPresent) { + return controllerFromStatusBar.get() + } + + centralSurfaces?.let { + // If the view is not in the status bar, then we are animating a view in the shade. + // We have to make sure that we collapse it when the animation ends or is cancelled. + if (dismissShade) { + return StatusBarLaunchAnimatorController( + animationController, + it, + isLaunchForActivity + ) + } + } + + return animationController + } + + /** Retrieves the current user handle to start the Activity. */ + private fun getActivityUserHandle(intent: Intent): UserHandle { + val packages: Array = + context.resources.getStringArray(R.array.system_ui_packages) + for (pkg in packages) { + if (intent.component == null) break + if (pkg == intent.component.packageName) { + return UserHandle(UserHandle.myUserId()) + } + } + return userTracker.userHandle + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 873f4f213dce4..4e690696b5d33 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -54,6 +54,7 @@ import com.android.systemui.shade.NotificationShadeWindowViewController; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.statusbar.LightRevealScrim; import com.android.systemui.statusbar.NotificationPresenter; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.util.Compile; import java.io.PrintWriter; @@ -612,4 +613,15 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { mDeviceId = deviceId; } } + + /** + * Sets launching activity over LS state in central surfaces. + */ + void setIsLaunchingActivityOverLockscreen(boolean isLaunchingActivityOverLockscreen); + + /** + * Gets an animation controller from a notification row. + */ + ActivityLaunchAnimator.Controller getAnimatorControllerFromNotification( + ExpandableNotificationRow associatedView); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 61e5a0018a5a1..12348a6cf36c2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -283,6 +283,9 @@ import javax.inject.Provider; * If at all possible, please avoid adding additional code to this monstrous class! Our goal is * to break up this class into many small classes, and any code added here will slow down that goal. * + * + * Note that ActivityStarter logic here is deprecated and should be added here as well as + * {@link ActivityStarterImpl} */ @SysUISingleton public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @@ -1800,23 +1803,27 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return (mDisabled1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0; } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade, int flags) { startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade, flags); } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void startActivity(Intent intent, boolean dismissShade) { startActivityDismissingKeyguard(intent, false /* onlyProvisioned */, dismissShade); } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void startActivity(Intent intent, boolean dismissShade, @androidx.annotation.Nullable ActivityLaunchAnimator.Controller animationController) { startActivity(intent, dismissShade, animationController, false); } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void startActivity(Intent intent, boolean dismissShade, @Nullable ActivityLaunchAnimator.Controller animationController, @@ -1825,6 +1832,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { getActivityUserHandle(intent)); } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void startActivity(Intent intent, boolean dismissShade, @Nullable ActivityLaunchAnimator.Controller animationController, @@ -2422,6 +2430,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return mDisplayId; } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned, boolean dismissShade, int flags) { @@ -2430,12 +2439,14 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { flags, null /* animationController */, getActivityUserHandle(intent)); } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned, boolean dismissShade) { startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade, 0); } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void startActivityDismissingKeyguard(Intent intent, boolean onlyProvisioned, boolean dismissShade, boolean disallowEnterPictureInPictureWhileLaunching, @@ -2447,6 +2458,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { userHandle, null /* customMessage */); } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned, final boolean dismissShade, final boolean disallowEnterPictureInPictureWhileLaunching, @@ -2557,6 +2569,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { * animation. This is ignored if {@code animationController} is not * animating in the shade window. * @param isLaunchForActivity whether the launch is for an activity. + * + * Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Nullable private ActivityLaunchAnimator.Controller wrapAnimationController( @@ -2586,6 +2600,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mStatusBarKeyguardViewManager.readyForKeyguardDone(); } + + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void executeRunnableDismissingKeyguard(final Runnable runnable, final Runnable cancelAction, @@ -2596,6 +2612,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { deferred, false /* willAnimateOnKeyguard */, null /* customMessage */); } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void executeRunnableDismissingKeyguard(final Runnable runnable, final Runnable cancelAction, @@ -2706,16 +2723,19 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { afterKeyguardGone /* afterKeyguardGone */); } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ protected void dismissKeyguardThenExecute(OnDismissAction action, boolean afterKeyguardGone) { dismissKeyguardThenExecute(action, null /* cancelRunnable */, afterKeyguardGone); } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction, boolean afterKeyguardGone) { dismissKeyguardThenExecute(action, cancelAction, afterKeyguardGone, null); } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction, boolean afterKeyguardGone, String customMessage) { @@ -2917,6 +2937,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { | ((currentlyInsecure ? 1 : 0) << 12); } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void postQSRunnableDismissingKeyguard(final Runnable runnable) { mMainExecutor.execute(() -> { @@ -2926,11 +2947,13 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { }); } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void postStartActivityDismissingKeyguard(PendingIntent intent) { postStartActivityDismissingKeyguard(intent, null /* animationController */); } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void postStartActivityDismissingKeyguard(final PendingIntent intent, @Nullable ActivityLaunchAnimator.Controller animationController) { @@ -2938,11 +2961,13 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { null /* intentSentUiThreadCallback */, animationController)); } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void postStartActivityDismissingKeyguard(final Intent intent, int delay) { postStartActivityDismissingKeyguard(intent, delay, null /* animationController */); } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void postStartActivityDismissingKeyguard(Intent intent, int delay, @Nullable ActivityLaunchAnimator.Controller animationController) { @@ -2950,6 +2975,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { null /* customMessage */); } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void postStartActivityDismissingKeyguard(Intent intent, int delay, @Nullable ActivityLaunchAnimator.Controller animationController, @@ -4100,11 +4126,13 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { dismissKeyguardThenExecute(onDismissAction, afterKeyguardGone); } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void startPendingIntentDismissingKeyguard(final PendingIntent intent) { startPendingIntentDismissingKeyguard(intent, null); } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void startPendingIntentDismissingKeyguard( final PendingIntent intent, @Nullable final Runnable intentSentUiThreadCallback) { @@ -4112,6 +4140,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { (ActivityLaunchAnimator.Controller) null); } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void startPendingIntentDismissingKeyguard(PendingIntent intent, Runnable intentSentUiThreadCallback, View associatedView) { @@ -4125,6 +4154,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { animationController); } + /** Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ @Override public void startPendingIntentDismissingKeyguard( final PendingIntent intent, @Nullable final Runnable intentSentUiThreadCallback, @@ -4548,6 +4578,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { * launched as user of the current process. * @param intent * @return UserHandle + * + * Logic is duplicated in {@link ActivityStarterImpl}. Please add it there too. */ private UserHandle getActivityUserHandle(Intent intent) { String[] packages = mContext.getResources().getStringArray(R.array.system_ui_packages); @@ -4570,4 +4602,15 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { && mBiometricUnlockController.getMode() != BiometricUnlockController.MODE_WAKE_AND_UNLOCK; } + + @Override + public void setIsLaunchingActivityOverLockscreen(boolean isLaunchingActivityOverLockscreen) { + mIsLaunchingActivityOverLockscreen = isLaunchingActivityOverLockscreen; + } + + @Override + public ActivityLaunchAnimator.Controller getAnimatorControllerFromNotification( + ExpandableNotificationRow associatedView) { + return mNotificationAnimationProvider.getAnimatorController(associatedView); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt new file mode 100644 index 0000000000000..b6b28c9e45270 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.statusbar.phone + +import android.app.PendingIntent +import android.content.Intent +import android.os.RemoteException +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.ActivityIntentHelper +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.assist.AssistManager +import com.android.systemui.keyguard.KeyguardViewMediator +import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.plugins.ActivityStarter.OnDismissAction +import com.android.systemui.settings.UserTracker +import com.android.systemui.shade.ShadeController +import com.android.systemui.statusbar.NotificationLockscreenUserManager +import com.android.systemui.statusbar.SysuiStatusBarStateController +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import dagger.Lazy +import java.util.Optional +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.anyBoolean +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class ActivityStarterImplTest : SysuiTestCase() { + @Mock private lateinit var centralSurfaces: CentralSurfaces + @Mock private lateinit var assistManager: AssistManager + @Mock private lateinit var dozeServiceHost: DozeServiceHost + @Mock private lateinit var biometricUnlockController: BiometricUnlockController + @Mock private lateinit var keyguardViewMediator: KeyguardViewMediator + @Mock private lateinit var shadeController: ShadeController + @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager + @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator + @Mock private lateinit var lockScreenUserManager: NotificationLockscreenUserManager + @Mock private lateinit var statusBarWindowController: StatusBarWindowController + @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle + @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController + @Mock private lateinit var userTracker: UserTracker + @Mock private lateinit var activityIntentHelper: ActivityIntentHelper + private lateinit var underTest: ActivityStarterImpl + private val mainExecutor = FakeExecutor(FakeSystemClock()) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + underTest = + ActivityStarterImpl( + Lazy { Optional.of(centralSurfaces) }, + Lazy { assistManager }, + Lazy { dozeServiceHost }, + Lazy { biometricUnlockController }, + Lazy { keyguardViewMediator }, + Lazy { shadeController }, + Lazy { statusBarKeyguardViewManager }, + activityLaunchAnimator, + context, + lockScreenUserManager, + statusBarWindowController, + wakefulnessLifecycle, + keyguardStateController, + statusBarStateController, + keyguardUpdateMonitor, + deviceProvisionedController, + userTracker, + activityIntentHelper, + mainExecutor, + ) + } + + @Test + fun startPendingIntentDismissingKeyguard_keyguardShowing_dismissWithAction() { + val pendingIntent = mock(PendingIntent::class.java) + whenever(keyguardStateController.isShowing).thenReturn(true) + whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) + + underTest.startPendingIntentDismissingKeyguard(pendingIntent) + + verify(statusBarKeyguardViewManager) + .dismissWithAction(any(OnDismissAction::class.java), eq(null), anyBoolean(), eq(null)) + } + + @Test + fun startPendingIntentDismissingKeyguard_associatedView_getAnimatorController() { + val pendingIntent = mock(PendingIntent::class.java) + val associatedView = mock(ExpandableNotificationRow::class.java) + + underTest.startPendingIntentDismissingKeyguard( + intent = pendingIntent, + intentSentUiThreadCallback = null, + associatedView = associatedView, + ) + + verify(centralSurfaces).getAnimatorControllerFromNotification(associatedView) + } + + @Test + fun startActivity_noUserHandleProvided_getUserHandle() { + val intent = mock(Intent::class.java) + + underTest.startActivity(intent, false) + + verify(userTracker).userHandle + } + + @Test + fun postStartActivityDismissingKeyguard_pendingIntent_postsOnMain() { + val intent = mock(PendingIntent::class.java) + + underTest.postStartActivityDismissingKeyguard(intent) + + assertThat(mainExecutor.numPending()).isEqualTo(1) + } + + @Test + fun postStartActivityDismissingKeyguard_intent_postsOnMain() { + val intent = mock(Intent::class.java) + + underTest.postStartActivityDismissingKeyguard(intent, 0) + + assertThat(mainExecutor.numPending()).isEqualTo(1) + } + + @Test + fun dismissKeyguardThenExecute_startWakeAndUnlock() { + whenever(wakefulnessLifecycle.wakefulness) + .thenReturn(WakefulnessLifecycle.WAKEFULNESS_ASLEEP) + whenever(keyguardStateController.canDismissLockScreen()).thenReturn(true) + whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false) + whenever(dozeServiceHost.isPulsing).thenReturn(true) + + underTest.dismissKeyguardThenExecute({ true }, {}, false) + + verify(biometricUnlockController) + .startWakeAndUnlock(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING) + } + + @Test + fun dismissKeyguardThenExecute_keyguardIsShowing_dismissWithAction() { + val customMessage = "Enter your pin." + whenever(keyguardStateController.isShowing).thenReturn(true) + + underTest.dismissKeyguardThenExecute({ true }, {}, false, customMessage) + + verify(statusBarKeyguardViewManager) + .dismissWithAction( + any(OnDismissAction::class.java), + any(Runnable::class.java), + eq(false), + eq(customMessage) + ) + } + + @Test + fun dismissKeyguardThenExecute_awakeDreams() { + val customMessage = "Enter your pin." + var dismissActionExecuted = false + whenever(keyguardStateController.isShowing).thenReturn(false) + whenever(keyguardUpdateMonitor.isDreaming).thenReturn(true) + + underTest.dismissKeyguardThenExecute( + { + dismissActionExecuted = true + true + }, + {}, + false, + customMessage + ) + + verify(centralSurfaces).awakenDreams() + assertThat(dismissActionExecuted).isTrue() + } + + @Test + @Throws(RemoteException::class) + fun executeRunnableDismissingKeyguard_dreaming_notShowing_awakenDreams() { + whenever(keyguardStateController.isShowing).thenReturn(false) + whenever(keyguardStateController.isOccluded).thenReturn(false) + whenever(keyguardUpdateMonitor.isDreaming).thenReturn(true) + + underTest.executeRunnableDismissingKeyguard( + runnable = {}, + cancelAction = null, + dismissShade = false, + afterKeyguardGone = false, + deferred = false + ) + + verify(centralSurfaces, times(1)).awakenDreams() + } + + @Test + @Throws(RemoteException::class) + fun executeRunnableDismissingKeyguard_notDreaming_notShowing_doNotAwakenDreams() { + whenever(keyguardStateController.isShowing).thenReturn(false) + whenever(keyguardStateController.isOccluded).thenReturn(false) + whenever(keyguardUpdateMonitor.isDreaming).thenReturn(false) + + underTest.executeRunnableDismissingKeyguard( + runnable = {}, + cancelAction = null, + dismissShade = false, + afterKeyguardGone = false, + deferred = false + ) + + verify(centralSurfaces, never()).awakenDreams() + } + + @Test + fun postQSRunnableDismissingKeyguard_leaveOpenStatusBarState() { + underTest.postQSRunnableDismissingKeyguard {} + + assertThat(mainExecutor.numPending()).isEqualTo(1) + mainExecutor.runAllReady() + verify(statusBarStateController).setLeaveOpenOnKeyguardHide(true) + } +}