Enable origin animations when launching the UMO over the Lockscreen.
Bug: 346865769
Flag: com.android.systemui.media_lockscreen_launch_animation
Test: atest LegacyActivityStarterInternalImplTest MediaControlPanelTest
Change-Id: I103f781edfd241094d58be93e0e585e6ff1545ed
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index ffa1db3..020bfe6 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1098,3 +1098,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "media_lockscreen_launch_animation"
+ namespace : "systemui"
+ description : "Enable the origin launch animation for UMO when opening on top of lockscreen."
+ bug : "346865769"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
index 1656a2e..5887f90 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
@@ -20,8 +20,11 @@
import android.app.PendingIntent
import android.content.Intent
import android.os.Bundle
+import android.os.Handler
import android.os.RemoteException
import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.view.View
import android.widget.FrameLayout
import android.window.SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR
@@ -29,6 +32,7 @@
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.animation.LaunchableView
@@ -36,7 +40,6 @@
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
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.shade.data.repository.FakeShadeRepository
@@ -51,11 +54,6 @@
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.argumentCaptor
-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
import java.util.Optional
@@ -64,14 +62,19 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.nullable
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.Mockito.`when`
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
@ExperimentalCoroutinesApi
@SmallTest
@@ -132,22 +135,22 @@
mainExecutor = mainExecutor,
communalSceneInteractor = communalSceneInteractor,
)
- whenever(userTracker.userHandle).thenReturn(UserHandle.OWNER)
- whenever(communalSceneInteractor.isIdleOnCommunal).thenReturn(MutableStateFlow(false))
+ `when`(userTracker.userHandle).thenReturn(UserHandle.OWNER)
+ `when`(communalSceneInteractor.isIdleOnCommunal).thenReturn(MutableStateFlow(false))
}
@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)
+ `when`(pendingIntent.isActivity).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
+ `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
underTest.startPendingIntentDismissingKeyguard(intent = pendingIntent, dismissShade = true)
mainExecutor.runAllReady()
verify(statusBarKeyguardViewManager)
- .dismissWithAction(any(OnDismissAction::class.java), eq(null), anyBoolean(), eq(null))
+ .dismissWithAction(any(), eq(null), anyBoolean(), eq(null))
}
@Test
@@ -160,10 +163,10 @@
}
parent.addView(view)
val controller = ActivityTransitionAnimator.Controller.fromView(view)
- whenever(pendingIntent.isActivity).thenReturn(true)
- whenever(keyguardStateController.isShowing).thenReturn(true)
- whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
- whenever(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt()))
+ `when`(pendingIntent.isActivity).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
+ `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+ `when`(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt()))
.thenReturn(true)
startPendingIntentMaybeDismissingKeyguard(
@@ -175,9 +178,9 @@
verify(activityTransitionAnimator)
.startPendingIntentWithAnimation(
- nullable(),
+ nullable(ActivityTransitionAnimator.Controller::class.java),
eq(true),
- nullable(),
+ nullable(String::class.java),
eq(true),
any(),
)
@@ -193,10 +196,10 @@
}
parent.addView(view)
val controller = ActivityTransitionAnimator.Controller.fromView(view)
- whenever(pendingIntent.isActivity).thenReturn(true)
- whenever(keyguardStateController.isShowing).thenReturn(true)
- whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
- whenever(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt()))
+ `when`(pendingIntent.isActivity).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
+ `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+ `when`(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt()))
.thenReturn(false)
// extra activity options to set on pending intent
@@ -220,12 +223,12 @@
eq(context),
eq(0),
eq(fillInIntent),
- nullable(),
- nullable(),
- nullable(),
+ nullable(PendingIntent.OnFinished::class.java),
+ nullable(Handler::class.java),
+ nullable(String::class.java),
bundleCaptor.capture()
)
- val options = ActivityOptions.fromBundle(bundleCaptor.value)
+ val options = ActivityOptions.fromBundle(bundleCaptor.firstValue)
assertThat(options.isPendingIntentBackgroundActivityLaunchAllowedByPermission).isFalse()
assertThat(options.splashScreenStyle).isEqualTo(SPLASH_SCREEN_STYLE_SOLID_COLOR)
}
@@ -245,6 +248,74 @@
verify(centralSurfaces).getAnimatorControllerFromNotification(associatedView)
}
+ @EnableFlags(Flags.FLAG_MEDIA_LOCKSCREEN_LAUNCH_ANIMATION)
+ @Test
+ fun startPendingIntentDismissingKeyguard_transitionAnimator_animateOverOcclusion() {
+ val parent = FrameLayout(context)
+ val view =
+ object : View(context), LaunchableView {
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {}
+ }
+ parent.addView(view)
+ val controller = ActivityTransitionAnimator.Controller.fromView(view)
+ val pendingIntent = mock(PendingIntent::class.java)
+ `when`(pendingIntent.isActivity).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
+ `when`(keyguardStateController.isOccluded).thenReturn(true)
+
+ underTest.startPendingIntentDismissingKeyguard(
+ intent = pendingIntent,
+ dismissShade = true,
+ animationController = controller,
+ showOverLockscreen = true,
+ skipLockscreenChecks = true
+ )
+ mainExecutor.runAllReady()
+
+ verify(activityTransitionAnimator)
+ .startPendingIntentWithAnimation(
+ nullable(ActivityTransitionAnimator.Controller::class.java),
+ eq(true),
+ nullable(String::class.java),
+ eq(true),
+ any(),
+ )
+ }
+
+ @DisableFlags(Flags.FLAG_MEDIA_LOCKSCREEN_LAUNCH_ANIMATION)
+ @Test
+ fun startPendingIntentDismissingKeyguard_transitionAnimator_doNotAnimateOverOcclusion() {
+ val parent = FrameLayout(context)
+ val view =
+ object : View(context), LaunchableView {
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {}
+ }
+ parent.addView(view)
+ val controller = ActivityTransitionAnimator.Controller.fromView(view)
+ val pendingIntent = mock(PendingIntent::class.java)
+ `when`(pendingIntent.isActivity).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
+ `when`(keyguardStateController.isOccluded).thenReturn(true)
+
+ underTest.startPendingIntentDismissingKeyguard(
+ intent = pendingIntent,
+ dismissShade = true,
+ animationController = controller,
+ showOverLockscreen = true,
+ skipLockscreenChecks = true
+ )
+ mainExecutor.runAllReady()
+
+ verify(activityTransitionAnimator)
+ .startPendingIntentWithAnimation(
+ nullable(ActivityTransitionAnimator.Controller::class.java),
+ eq(false),
+ nullable(String::class.java),
+ eq(true),
+ any(),
+ )
+ }
+
@Test
fun startActivity_noUserHandleProvided_getUserHandle() {
val intent = mock(Intent::class.java)
@@ -254,13 +325,66 @@
verify(userTracker).userHandle
}
+ @EnableFlags(Flags.FLAG_MEDIA_LOCKSCREEN_LAUNCH_ANIMATION)
+ @Test
+ fun startActivity_transitionAnimator_animateOverOcclusion() {
+ val intent = mock(Intent::class.java)
+ val parent = FrameLayout(context)
+ val view =
+ object : View(context), LaunchableView {
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {}
+ }
+ parent.addView(view)
+ val controller = ActivityTransitionAnimator.Controller.fromView(view)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
+ `when`(keyguardStateController.isOccluded).thenReturn(true)
+
+ mainExecutor.runAllReady()
+ underTest.startActivity(intent, true, controller, true, null)
+
+ verify(activityTransitionAnimator)
+ .startIntentWithAnimation(
+ nullable(ActivityTransitionAnimator.Controller::class.java),
+ eq(true),
+ nullable(String::class.java),
+ eq(true),
+ any(),
+ )
+ }
+
+ @DisableFlags(Flags.FLAG_MEDIA_LOCKSCREEN_LAUNCH_ANIMATION)
+ @Test
+ fun startActivity_transitionAnimator_doNotAnimateOverOcclusion() {
+ val intent = mock(Intent::class.java)
+ val parent = FrameLayout(context)
+ val view =
+ object : View(context), LaunchableView {
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {}
+ }
+ parent.addView(view)
+ val controller = ActivityTransitionAnimator.Controller.fromView(view)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
+ `when`(keyguardStateController.isOccluded).thenReturn(true)
+
+ mainExecutor.runAllReady()
+ underTest.startActivity(intent, true, controller, true, null)
+
+ verify(activityTransitionAnimator)
+ .startIntentWithAnimation(
+ nullable(ActivityTransitionAnimator.Controller::class.java),
+ eq(false),
+ nullable(String::class.java),
+ eq(true),
+ any(),
+ )
+ }
+
@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)
+ `when`(wakefulnessLifecycle.wakefulness).thenReturn(WakefulnessLifecycle.WAKEFULNESS_ASLEEP)
+ `when`(keyguardStateController.canDismissLockScreen()).thenReturn(true)
+ `when`(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
+ `when`(dozeServiceHost.isPulsing).thenReturn(true)
underTest.dismissKeyguardThenExecute({ true }, {}, false)
@@ -271,25 +395,20 @@
@Test
fun dismissKeyguardThenExecute_keyguardIsShowing_dismissWithAction() {
val customMessage = "Enter your pin."
- whenever(keyguardStateController.isShowing).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
underTest.dismissKeyguardThenExecute({ true }, {}, false, customMessage)
verify(statusBarKeyguardViewManager)
- .dismissWithAction(
- any(OnDismissAction::class.java),
- any(Runnable::class.java),
- eq(false),
- eq(customMessage)
- )
+ .dismissWithAction(any(), any(), 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)
+ `when`(keyguardStateController.isShowing).thenReturn(false)
+ `when`(keyguardUpdateMonitor.isDreaming).thenReturn(true)
underTest.dismissKeyguardThenExecute(
{
@@ -308,9 +427,9 @@
@Test
@Throws(RemoteException::class)
fun executeRunnableDismissingKeyguard_dreaming_notShowing_awakenDreams() {
- whenever(keyguardStateController.isShowing).thenReturn(false)
- whenever(keyguardStateController.isOccluded).thenReturn(false)
- whenever(keyguardUpdateMonitor.isDreaming).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(false)
+ `when`(keyguardStateController.isOccluded).thenReturn(false)
+ `when`(keyguardUpdateMonitor.isDreaming).thenReturn(true)
underTest.executeRunnableDismissingKeyguard(
runnable = {},
@@ -326,9 +445,9 @@
@Test
@Throws(RemoteException::class)
fun executeRunnableDismissingKeyguard_notDreaming_notShowing_doNotAwakenDreams() {
- whenever(keyguardStateController.isShowing).thenReturn(false)
- whenever(keyguardStateController.isOccluded).thenReturn(false)
- whenever(keyguardUpdateMonitor.isDreaming).thenReturn(false)
+ `when`(keyguardStateController.isShowing).thenReturn(false)
+ `when`(keyguardStateController.isOccluded).thenReturn(false)
+ `when`(keyguardUpdateMonitor.isDreaming).thenReturn(false)
underTest.executeRunnableDismissingKeyguard(
runnable = {},
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 5ec4f88..846c596 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -19,6 +19,7 @@
import static android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS;
import static com.android.settingslib.flags.Flags.legacyLeAudioSharing;
+import static com.android.systemui.Flags.mediaLockscreenLaunchAnimation;
import static com.android.systemui.media.controls.shared.model.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS;
import android.animation.Animator;
@@ -577,13 +578,23 @@
&& mActivityIntentHelper.wouldPendingShowOverLockscreen(clickIntent,
mLockscreenUserManager.getCurrentUserId());
if (showOverLockscreen) {
- try {
- ActivityOptions opts = ActivityOptions.makeBasic();
- opts.setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
- clickIntent.send(opts.toBundle());
- } catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Pending intent for " + key + " was cancelled");
+ if (mediaLockscreenLaunchAnimation()) {
+ mActivityStarter.startPendingIntentMaybeDismissingKeyguard(
+ clickIntent,
+ /* dismissShade = */ true,
+ /* intentSentUiThreadCallback = */ null,
+ buildLaunchAnimatorController(mMediaViewHolder.getPlayer()),
+ /* fillIntent = */ null,
+ /* extraOptions = */ null);
+ } else {
+ try {
+ ActivityOptions opts = ActivityOptions.makeBasic();
+ opts.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ clickIntent.send(opts.toBundle());
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, "Pending intent for " + key + " was cancelled");
+ }
}
} else {
mActivityStarter.postStartActivityDismissingKeyguard(clickIntent,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index 639560f..e96326a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -34,6 +34,7 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.Flags.communalHub
+import com.android.systemui.Flags.mediaLockscreenLaunchAnimation
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.animation.DelegateTransitionAnimatorController
import com.android.systemui.assist.AssistManager
@@ -635,8 +636,9 @@
isActivityIntent: Boolean,
showOverLockscreen: Boolean,
): Boolean {
- // TODO(b/294418322): Support launch animations when occluded.
- if (keyguardStateController.isOccluded) {
+ // TODO(b/294418322): always support launch animations when occluded.
+ val ignoreOcclusion = showOverLockscreen && mediaLockscreenLaunchAnimation()
+ if (keyguardStateController.isOccluded && !ignoreOcclusion) {
return false
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index ecc456c..a770722 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -40,6 +40,7 @@
import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.os.Bundle
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.DeviceFlagsValueProvider
@@ -1771,8 +1772,40 @@
verify(logger).logSeek(anyInt(), eq(PACKAGE), eq(instanceId))
}
+ @EnableFlags(Flags.FLAG_MEDIA_LOCKSCREEN_LAUNCH_ANIMATION)
@Test
- fun tapContentView_showOverLockscreen_openActivity() {
+ fun tapContentView_showOverLockscreen_openActivity_withOriginAnimation() {
+ // WHEN we are on lockscreen and this activity can show over lockscreen
+ whenever(keyguardStateController.isShowing).thenReturn(true)
+ whenever(activityIntentHelper.wouldPendingShowOverLockscreen(any(), any())).thenReturn(true)
+
+ val clickIntent = mock(Intent::class.java)
+ val pendingIntent = mock(PendingIntent::class.java)
+ whenever(pendingIntent.intent).thenReturn(clickIntent)
+ val captor = ArgumentCaptor.forClass(View.OnClickListener::class.java)
+ val data = mediaData.copy(clickIntent = pendingIntent)
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(data, KEY)
+ verify(viewHolder.player).setOnClickListener(captor.capture())
+
+ // THEN it sends the PendingIntent without dismissing keyguard first,
+ // and does not use the Intent directly (see b/271845008)
+ captor.value.onClick(viewHolder.player)
+ verify(activityStarter)
+ .startPendingIntentMaybeDismissingKeyguard(
+ eq(pendingIntent),
+ eq(true),
+ eq(null),
+ any(),
+ eq(null),
+ eq(null),
+ )
+ verify(activityStarter, never()).postStartActivityDismissingKeyguard(eq(clickIntent), any())
+ }
+
+ @DisableFlags(Flags.FLAG_MEDIA_LOCKSCREEN_LAUNCH_ANIMATION)
+ @Test
+ fun tapContentView_showOverLockscreen_openActivity_withoutOriginAnimation() {
// WHEN we are on lockscreen and this activity can show over lockscreen
whenever(keyguardStateController.isShowing).thenReturn(true)
whenever(activityIntentHelper.wouldPendingShowOverLockscreen(any(), any())).thenReturn(true)