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)