Merge "Properly launch activities that show over lock" into main
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 4ea57a8..ab4db45 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -290,9 +290,10 @@
controller: Controller?,
animate: Boolean = true,
packageName: String? = null,
+ showOverLockscreen: Boolean = false,
intentStarter: PendingIntentStarter
) {
- startIntentWithAnimation(controller, animate, packageName) {
+ startIntentWithAnimation(controller, animate, packageName, showOverLockscreen) {
intentStarter.startPendingIntent(it)
}
}
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 9cc87fd..f0e3c99 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -57,6 +57,16 @@
@Nullable ActivityLaunchAnimator.Controller animationController);
/**
+ * Similar to {@link #startPendingIntentDismissingKeyguard}, except that it supports launching
+ * activities on top of the keyguard. If the activity supports {@code showOverLockscreen}, it
+ * will show over keyguard without first dimissing it. If it doesn't support it, calling this
+ * method is exactly the same as calling {@link #startPendingIntentDismissingKeyguard}.
+ */
+ void startPendingIntentMaybeDismissingKeyguard(PendingIntent intent,
+ @Nullable Runnable intentSentUiThreadCallback,
+ @Nullable ActivityLaunchAnimator.Controller animationController);
+
+ /**
* The intent flag can be specified in startActivity().
*/
void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade, int flags);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 051eeb0..bd13d06 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -48,6 +48,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.animation.ActivityLaunchAnimator;
@@ -538,12 +539,17 @@
Log.i(TAG, "Launching activity before click");
} else {
Log.i(TAG, "The activity is starting");
- ActivityLaunchAnimator.Controller controller = mViewClicked == null
- ? null
- : ActivityLaunchAnimator.Controller.fromView(mViewClicked, 0);
- mUiHandler.post(() ->
- mActivityStarter.startPendingIntentDismissingKeyguard(
- pendingIntent, null, controller)
+
+ ActivityLaunchAnimator.Controller controller =
+ mViewClicked == null ? null :
+ ActivityLaunchAnimator.Controller.fromView(
+ mViewClicked,
+ InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
+ );
+ mActivityStarter.startPendingIntentMaybeDismissingKeyguard(
+ pendingIntent,
+ /* intentSentUiThreadCallback= */ null,
+ controller
);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 07d3a1c..2d125462 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -30,7 +30,6 @@
import android.view.WindowManager
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.ActivityIntentHelper
-import com.android.systemui.res.R
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.ActivityLaunchAnimator.PendingIntentStarter
import com.android.systemui.animation.DelegateLaunchAnimatorController
@@ -43,6 +42,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeViewController
@@ -134,6 +134,19 @@
)
}
+ override fun startPendingIntentMaybeDismissingKeyguard(
+ intent: PendingIntent,
+ intentSentUiThreadCallback: Runnable?,
+ animationController: ActivityLaunchAnimator.Controller?
+ ) {
+ activityStarterInternal.startPendingIntentDismissingKeyguard(
+ intent = intent,
+ intentSentUiThreadCallback = intentSentUiThreadCallback,
+ animationController = animationController,
+ showOverLockscreen = true,
+ )
+ }
+
/**
* TODO(b/279084380): Change callers to just call startActivityDismissingKeyguard and deprecate
* this.
@@ -454,7 +467,7 @@
!willLaunchResolverActivity &&
shouldAnimateLaunch(isActivityIntent = true)
val animController =
- wrapAnimationController(
+ wrapAnimationControllerForShadeOrStatusBar(
animationController = animationController,
dismissShade = dismissShade,
isLaunchForActivity = true,
@@ -547,12 +560,18 @@
)
}
- /** Starts a pending intent after dismissing keyguard. */
+ /**
+ * Starts a pending intent after dismissing keyguard.
+ *
+ * This can be called in a background thread (to prevent calls in [ActivityIntentHelper] in
+ * the main thread).
+ */
fun startPendingIntentDismissingKeyguard(
intent: PendingIntent,
intentSentUiThreadCallback: Runnable? = null,
associatedView: View? = null,
animationController: ActivityLaunchAnimator.Controller? = null,
+ showOverLockscreen: Boolean = false,
) {
val animationController =
if (associatedView is ExpandableNotificationRow) {
@@ -566,79 +585,103 @@
lockScreenUserManager.currentUserId,
))
+ val actuallyShowOverLockscreen =
+ showOverLockscreen &&
+ intent.isActivity &&
+ activityIntentHelper.wouldPendingShowOverLockscreen(
+ intent,
+ lockScreenUserManager.currentUserId
+ )
+
val animate =
!willLaunchResolverActivity &&
animationController != null &&
- shouldAnimateLaunch(intent.isActivity)
+ shouldAnimateLaunch(intent.isActivity, actuallyShowOverLockscreen)
+
+ // We wrap animationCallback with a StatusBarLaunchAnimatorController so
+ // that the shade is collapsed after the animation (or when it is cancelled,
+ // aborted, etc).
+ val statusBarController =
+ wrapAnimationControllerForShadeOrStatusBar(
+ animationController = animationController,
+ dismissShade = true,
+ isLaunchForActivity = intent.isActivity,
+ )
+ val controller =
+ if (actuallyShowOverLockscreen) {
+ wrapAnimationControllerForLockscreen(statusBarController)
+ } else {
+ statusBarController
+ }
// 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(
- displayId,
- animationAdapter
- )
+ val runnable = Runnable {
+ try {
+ activityLaunchAnimator.startPendingIntentWithAnimation(
+ controller,
+ animate,
+ intent.creatorPackage,
+ actuallyShowOverLockscreen,
+ object : PendingIntentStarter {
+ override fun startPendingIntent(
+ animationAdapter: RemoteAnimationAdapter?
+ ): Int {
+ val options =
+ ActivityOptions(
+ CentralSurfaces.getActivityOptions(
+ displayId,
+ animationAdapter
)
- // TODO b/221255671: restrict this to only be set for
- // notifications
- options.isEligibleForLegacyPermissionPrompt = true
- options.setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
)
- 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.
- shadeControllerLazy.get().collapseOnMainThread()
- }
- // TODO: Dismiss Keyguard.
+ // TODO b/221255671: restrict this to only be set for
+ // notifications
+ options.isEligibleForLegacyPermissionPrompt = true
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
+ 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.
+ shadeControllerLazy.get().collapseOnMainThread()
}
- if (intent.isActivity) {
- assistManagerLazy.get().hideAssist()
- }
- intentSentUiThreadCallback?.let { postOnUiThread(runnable = it) }
- },
- afterKeyguardGone = willLaunchResolverActivity,
- dismissShade = collapse,
- willAnimateOnKeyguard = animate,
- )
+ // TODO: Dismiss Keyguard.
+ }
+ if (intent.isActivity) {
+ assistManagerLazy.get().hideAssist()
+ }
+ intentSentUiThreadCallback?.let { postOnUiThread(runnable = it) }
+ }
+
+ if (!actuallyShowOverLockscreen) {
+ postOnUiThread(delay = 0) {
+ executeRunnableDismissingKeyguard(
+ runnable = runnable,
+ afterKeyguardGone = willLaunchResolverActivity,
+ dismissShade = collapse,
+ willAnimateOnKeyguard = animate,
+ )
+ }
+ } else {
+ postOnUiThread(delay = 0, runnable)
+ }
}
/** Starts an Activity. */
@@ -678,71 +721,12 @@
// Wrap the animation controller to dismiss the shade and set
// mIsLaunchingActivityOverLockscreen during the animation.
val delegate =
- wrapAnimationController(
+ wrapAnimationControllerForShadeOrStatusBar(
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)
- }
- }
- }
+ controller = wrapAnimationControllerForLockscreen(delegate)
} 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.
@@ -874,7 +858,7 @@
* window.
* @param isLaunchForActivity whether the launch is for an activity.
*/
- private fun wrapAnimationController(
+ private fun wrapAnimationControllerForShadeOrStatusBar(
animationController: ActivityLaunchAnimator.Controller?,
dismissShade: Boolean,
isLaunchForActivity: Boolean,
@@ -909,6 +893,72 @@
return animationController
}
+ /**
+ * Wraps an animation controller so that if an activity would be launched on top of the
+ * lockscreen, the correct flags are set for it to be occluded.
+ */
+ private fun wrapAnimationControllerForLockscreen(
+ animationController: ActivityLaunchAnimator.Controller?
+ ): ActivityLaunchAnimator.Controller? {
+ return animationController?.let {
+ object : DelegateLaunchAnimatorController(it) {
+ 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)
+ }
+ }
+ }
+ }
+
/** Retrieves the current user handle to start the Activity. */
private fun getActivityUserHandle(intent: Intent): UserHandle {
val packages: Array<String> =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index 43cf1b5..ae47a7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -37,7 +37,6 @@
import android.view.IWindowManager
import android.view.View
import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.view.LaunchableFrameLayout
@@ -48,6 +47,7 @@
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.res.R
import com.android.systemui.settings.FakeDisplayTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -343,8 +343,7 @@
testableLooper.processAllMessages()
verify(activityStarter, never())
- .startPendingIntentDismissingKeyguard(
- any(), any(), any(ActivityLaunchAnimator.Controller::class.java))
+ .startPendingIntentMaybeDismissingKeyguard(any(), nullable(), nullable())
}
@Test
@@ -357,8 +356,7 @@
testableLooper.processAllMessages()
verify(activityStarter, never())
- .startPendingIntentDismissingKeyguard(
- any(), any(), any(ActivityLaunchAnimator.Controller::class.java))
+ .startPendingIntentMaybeDismissingKeyguard(any(), nullable(), nullable())
}
@Test
@@ -373,7 +371,7 @@
testableLooper.processAllMessages()
verify(activityStarter)
- .startPendingIntentDismissingKeyguard(
+ .startPendingIntentMaybeDismissingKeyguard(
eq(pi), nullable(), nullable<ActivityLaunchAnimator.Controller>())
}
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
index 68f2728..7de05ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -19,11 +19,14 @@
import android.os.RemoteException
import android.os.UserHandle
import android.testing.AndroidTestingRunner
+import android.view.View
+import android.widget.FrameLayout
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.animation.LaunchableView
import com.android.systemui.assist.AssistManager
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
@@ -41,6 +44,7 @@
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.nullable
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -49,6 +53,7 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.mock
@@ -116,16 +121,51 @@
@Test
fun startPendingIntentDismissingKeyguard_keyguardShowing_dismissWithAction() {
val pendingIntent = mock(PendingIntent::class.java)
+ whenever(pendingIntent.isActivity).thenReturn(true)
whenever(keyguardStateController.isShowing).thenReturn(true)
whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
underTest.startPendingIntentDismissingKeyguard(pendingIntent)
+ mainExecutor.runAllReady()
verify(statusBarKeyguardViewManager)
.dismissWithAction(any(OnDismissAction::class.java), eq(null), anyBoolean(), eq(null))
}
@Test
+ fun startPendingIntentMaybeDismissingKeyguard_keyguardShowing_showOverLockscreen_activityLaunchAnimator() {
+ val pendingIntent = mock(PendingIntent::class.java)
+ val parent = FrameLayout(context)
+ val view =
+ object : View(context), LaunchableView {
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {}
+ }
+ parent.addView(view)
+ val controller = ActivityLaunchAnimator.Controller.fromView(view)
+ whenever(pendingIntent.isActivity).thenReturn(true)
+ whenever(keyguardStateController.isShowing).thenReturn(true)
+ whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+ whenever(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt()))
+ .thenReturn(true)
+
+ underTest.startPendingIntentMaybeDismissingKeyguard(
+ intent = pendingIntent,
+ animationController = controller,
+ intentSentUiThreadCallback = null,
+ )
+ mainExecutor.runAllReady()
+
+ verify(activityLaunchAnimator)
+ .startPendingIntentWithAnimation(
+ nullable(),
+ eq(true),
+ nullable(),
+ eq(true),
+ any(),
+ )
+ }
+
+ @Test
fun startPendingIntentDismissingKeyguard_associatedView_getAnimatorController() {
val pendingIntent = mock(PendingIntent::class.java)
val associatedView = mock(ExpandableNotificationRow::class.java)