Merge changes I4ea03743,I84993c71 into main

* changes:
  [SB][Screen Chips] Immediately hide chip after stopping via dialog.
  [SB][Screen Chips] Re-add animation from chip -> stop dialog, and CUJs.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index 11ccdff..59fd0ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -57,7 +57,7 @@
         interactor.ongoingCallState
             .map { state ->
                 when (state) {
-                    is OngoingCallModel.NoCall -> OngoingActivityChipModel.Hidden
+                    is OngoingCallModel.NoCall -> OngoingActivityChipModel.Hidden()
                     is OngoingCallModel.InCall -> {
                         // This block mimics OngoingCallController#updateChip.
                         if (state.startTimeMs <= 0L) {
@@ -82,7 +82,7 @@
                     }
                 }
             }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
+            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden())
 
     private fun getOnClickListener(state: OngoingCallModel.InCall): View.OnClickListener? {
         if (state.intent == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
index bafec38..6ea72b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
@@ -44,9 +44,10 @@
             // No custom on-click, because the dialog will automatically be dismissed when the
             // button is clicked anyway.
             setNegativeButton(R.string.close_dialog_button, /* onClick= */ null)
-            setPositiveButton(R.string.cast_to_other_device_stop_dialog_button) { _, _ ->
-                stopAction.invoke()
-            }
+            setPositiveButton(
+                R.string.cast_to_other_device_stop_dialog_button,
+                endMediaProjectionDialogHelper.wrapStopAction(stopAction),
+            )
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt
index 7dc9b25..b0c8321 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt
@@ -55,9 +55,10 @@
             // No custom on-click, because the dialog will automatically be dismissed when the
             // button is clicked anyway.
             setNegativeButton(R.string.close_dialog_button, /* onClick= */ null)
-            setPositiveButton(R.string.cast_to_other_device_stop_dialog_button) { _, _ ->
-                stopAction.invoke()
-            }
+            setPositiveButton(
+                R.string.cast_to_other_device_stop_dialog_button,
+                endMediaProjectionDialogHelper.wrapStopAction(stopAction),
+            )
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
index afa9cce..d9b0504 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
@@ -18,6 +18,9 @@
 
 import android.content.Context
 import androidx.annotation.DrawableRes
+import com.android.internal.jank.Cuj
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
@@ -35,6 +38,7 @@
 import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
 import com.android.systemui.util.time.SystemClock
@@ -60,6 +64,7 @@
     private val mediaProjectionChipInteractor: MediaProjectionChipInteractor,
     private val mediaRouterChipInteractor: MediaRouterChipInteractor,
     private val systemClock: SystemClock,
+    private val dialogTransitionAnimator: DialogTransitionAnimator,
     private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
     @StatusBarChipsLog private val logger: LogBuffer,
 ) : OngoingActivityChipViewModel {
@@ -74,18 +79,18 @@
         mediaProjectionChipInteractor.projection
             .map { projectionModel ->
                 when (projectionModel) {
-                    is ProjectionChipModel.NotProjecting -> OngoingActivityChipModel.Hidden
+                    is ProjectionChipModel.NotProjecting -> OngoingActivityChipModel.Hidden()
                     is ProjectionChipModel.Projecting -> {
                         if (projectionModel.type != ProjectionChipModel.Type.CAST_TO_OTHER_DEVICE) {
-                            OngoingActivityChipModel.Hidden
+                            OngoingActivityChipModel.Hidden()
                         } else {
                             createCastScreenToOtherDeviceChip(projectionModel)
                         }
                     }
                 }
             }
-            // See b/347726238.
-            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden)
+            // See b/347726238 for [SharingStarted.Lazily] reasoning.
+            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden())
 
     /**
      * The cast chip to show, based only on MediaRouter API events.
@@ -109,7 +114,7 @@
         mediaRouterChipInteractor.mediaRouterCastingState
             .map { routerModel ->
                 when (routerModel) {
-                    is MediaRouterCastModel.DoingNothing -> OngoingActivityChipModel.Hidden
+                    is MediaRouterCastModel.DoingNothing -> OngoingActivityChipModel.Hidden()
                     is MediaRouterCastModel.Casting -> {
                         // A consequence of b/269975671 is that MediaRouter will mark a device as
                         // casting before casting has actually started. To alleviate this bug a bit,
@@ -123,9 +128,9 @@
                     }
                 }
             }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
+            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden())
 
-    override val chip: StateFlow<OngoingActivityChipModel> =
+    private val internalChip: StateFlow<OngoingActivityChipModel> =
         combine(projectionChip, routerChip) { projection, router ->
                 logger.log(
                     TAG,
@@ -159,17 +164,24 @@
                     router
                 }
             }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
+            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden())
+
+    private val hideChipDuringDialogTransitionHelper = ChipTransitionHelper(scope)
+
+    override val chip: StateFlow<OngoingActivityChipModel> =
+        hideChipDuringDialogTransitionHelper.createChipFlow(internalChip)
 
     /** Stops the currently active projection. */
-    private fun stopProjecting() {
-        logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested (projection)" })
+    private fun stopProjectingFromDialog() {
+        logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested from dialog (projection)" })
+        hideChipDuringDialogTransitionHelper.onActivityStoppedFromDialog()
         mediaProjectionChipInteractor.stopProjecting()
     }
 
     /** Stops the currently active media route. */
-    private fun stopMediaRouterCasting() {
-        logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested (router)" })
+    private fun stopMediaRouterCastingFromDialog() {
+        logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested from dialog (router)" })
+        hideChipDuringDialogTransitionHelper.onActivityStoppedFromDialog()
         mediaRouterChipInteractor.stopCasting()
     }
 
@@ -190,6 +202,8 @@
             startTimeMs = systemClock.elapsedRealtime(),
             createDialogLaunchOnClickListener(
                 createCastScreenToOtherDeviceDialogDelegate(state),
+                dialogTransitionAnimator,
+                DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Cast to other device"),
                 logger,
                 TAG,
             ),
@@ -207,6 +221,11 @@
             colors = ColorsModel.Red,
             createDialogLaunchOnClickListener(
                 createGenericCastToOtherDeviceDialogDelegate(deviceName),
+                dialogTransitionAnimator,
+                DialogCuj(
+                    Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP,
+                    tag = "Cast to other device audio only",
+                ),
                 logger,
                 TAG,
             ),
@@ -219,7 +238,7 @@
         EndCastScreenToOtherDeviceDialogDelegate(
             endMediaProjectionDialogHelper,
             context,
-            stopAction = this::stopProjecting,
+            stopAction = this::stopProjectingFromDialog,
             state,
         )
 
@@ -228,7 +247,7 @@
             endMediaProjectionDialogHelper,
             context,
             deviceName,
-            stopAction = this::stopMediaRouterCasting,
+            stopAction = this::stopMediaRouterCastingFromDialog,
         )
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt
index 6004365..2d9ccb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt
@@ -17,7 +17,9 @@
 package com.android.systemui.statusbar.chips.mediaprojection.ui.view
 
 import android.app.ActivityManager
+import android.content.DialogInterface
 import android.content.pm.PackageManager
+import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.mediaprojection.data.model.MediaProjectionState
 import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -29,6 +31,7 @@
 @Inject
 constructor(
     private val dialogFactory: SystemUIDialog.Factory,
+    private val dialogTransitionAnimator: DialogTransitionAnimator,
     private val packageManager: PackageManager,
 ) {
     /** Creates a new [SystemUIDialog] using the given delegate. */
@@ -36,6 +39,28 @@
         return dialogFactory.create(delegate)
     }
 
+    /**
+     * Returns the click listener that should be invoked if a user clicks "Stop" on the end media
+     * projection dialog.
+     *
+     * The click listener will invoke [stopAction] and also do some UI manipulation.
+     *
+     * @param stopAction an action that, when invoked, should notify system API(s) that the media
+     *   projection should be stopped.
+     */
+    fun wrapStopAction(stopAction: () -> Unit): DialogInterface.OnClickListener {
+        return DialogInterface.OnClickListener { _, _ ->
+            // If the projection is stopped, then the chip will disappear, so we don't want the
+            // dialog to animate back into the chip just for the chip to disappear in a few frames.
+            dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations()
+            stopAction.invoke()
+            // TODO(b/332662551): If the projection is stopped, there's a brief moment where the
+            // dialog closes and the chip re-shows because the system APIs haven't come back and
+            // told SysUI that the projection has officially stopped. It would be great for the chip
+            // to not re-show at all.
+        }
+    }
+
     fun getAppName(state: MediaProjectionState.Projecting): CharSequence? {
         val specificTaskInfo =
             if (state is MediaProjectionState.Projecting.SingleTask) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt
index 1eca827..72656ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt
@@ -52,9 +52,10 @@
             // No custom on-click, because the dialog will automatically be dismissed when the
             // button is clicked anyway.
             setNegativeButton(R.string.close_dialog_button, /* onClick= */ null)
-            setPositiveButton(R.string.screenrecord_stop_dialog_button) { _, _ ->
-                stopAction.invoke()
-            }
+            setPositiveButton(
+                R.string.screenrecord_stop_dialog_button,
+                endMediaProjectionDialogHelper.wrapStopAction(stopAction),
+            )
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
index 0c34981..fcf3de4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
@@ -19,6 +19,9 @@
 import android.app.ActivityManager
 import android.content.Context
 import androidx.annotation.DrawableRes
+import com.android.internal.jank.Cuj
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
@@ -32,8 +35,10 @@
 import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.ScreenRecordChipInteractor
 import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel
 import com.android.systemui.statusbar.chips.screenrecord.ui.view.EndScreenRecordingDialogDelegate
+import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
 import com.android.systemui.util.time.SystemClock
@@ -52,15 +57,18 @@
     @Application private val scope: CoroutineScope,
     private val context: Context,
     private val interactor: ScreenRecordChipInteractor,
+    private val shareToAppChipViewModel: ShareToAppChipViewModel,
     private val systemClock: SystemClock,
     private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+    private val dialogTransitionAnimator: DialogTransitionAnimator,
     @StatusBarChipsLog private val logger: LogBuffer,
 ) : OngoingActivityChipViewModel {
-    override val chip: StateFlow<OngoingActivityChipModel> =
+
+    private val internalChip =
         interactor.screenRecordState
             .map { state ->
                 when (state) {
-                    is ScreenRecordChipModel.DoingNothing -> OngoingActivityChipModel.Hidden
+                    is ScreenRecordChipModel.DoingNothing -> OngoingActivityChipModel.Hidden()
                     is ScreenRecordChipModel.Starting -> {
                         OngoingActivityChipModel.Shown.Countdown(
                             colors = ColorsModel.Red,
@@ -80,6 +88,11 @@
                             startTimeMs = systemClock.elapsedRealtime(),
                             createDialogLaunchOnClickListener(
                                 createDelegate(state.recordedTask),
+                                dialogTransitionAnimator,
+                                DialogCuj(
+                                    Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP,
+                                    tag = "Screen record",
+                                ),
                                 logger,
                                 TAG,
                             ),
@@ -87,8 +100,13 @@
                     }
                 }
             }
-            // See b/347726238.
-            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden)
+            // See b/347726238 for [SharingStarted.Lazily] reasoning.
+            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden())
+
+    private val chipTransitionHelper = ChipTransitionHelper(scope)
+
+    override val chip: StateFlow<OngoingActivityChipModel> =
+        chipTransitionHelper.createChipFlow(internalChip)
 
     private fun createDelegate(
         recordedTask: ActivityManager.RunningTaskInfo?
@@ -96,13 +114,15 @@
         return EndScreenRecordingDialogDelegate(
             endMediaProjectionDialogHelper,
             context,
-            stopAction = this::stopRecording,
+            stopAction = this::stopRecordingFromDialog,
             recordedTask,
         )
     }
 
-    private fun stopRecording() {
-        logger.log(TAG, LogLevel.INFO, {}, { "Stop recording requested" })
+    private fun stopRecordingFromDialog() {
+        logger.log(TAG, LogLevel.INFO, {}, { "Stop recording requested from dialog" })
+        chipTransitionHelper.onActivityStoppedFromDialog()
+        shareToAppChipViewModel.onRecordingStoppedFromDialog()
         interactor.stopRecording()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegate.kt
index 564f20e..d10bd77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegate.kt
@@ -44,9 +44,10 @@
             // No custom on-click, because the dialog will automatically be dismissed when the
             // button is clicked anyway.
             setNegativeButton(R.string.close_dialog_button, /* onClick= */ null)
-            setPositiveButton(R.string.share_to_app_stop_dialog_button) { _, _ ->
-                stopAction.invoke()
-            }
+            setPositiveButton(
+                R.string.share_to_app_stop_dialog_button,
+                endMediaProjectionDialogHelper.wrapStopAction(stopAction),
+            )
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
index ddebd3a..85973fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
@@ -18,6 +18,9 @@
 
 import android.content.Context
 import androidx.annotation.DrawableRes
+import com.android.internal.jank.Cuj
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
@@ -32,6 +35,7 @@
 import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndShareToAppDialogDelegate
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
 import com.android.systemui.util.time.SystemClock
@@ -55,28 +59,49 @@
     private val mediaProjectionChipInteractor: MediaProjectionChipInteractor,
     private val systemClock: SystemClock,
     private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+    private val dialogTransitionAnimator: DialogTransitionAnimator,
     @StatusBarChipsLog private val logger: LogBuffer,
 ) : OngoingActivityChipViewModel {
-    override val chip: StateFlow<OngoingActivityChipModel> =
+    private val internalChip =
         mediaProjectionChipInteractor.projection
             .map { projectionModel ->
                 when (projectionModel) {
-                    is ProjectionChipModel.NotProjecting -> OngoingActivityChipModel.Hidden
+                    is ProjectionChipModel.NotProjecting -> OngoingActivityChipModel.Hidden()
                     is ProjectionChipModel.Projecting -> {
                         if (projectionModel.type != ProjectionChipModel.Type.SHARE_TO_APP) {
-                            OngoingActivityChipModel.Hidden
+                            OngoingActivityChipModel.Hidden()
                         } else {
                             createShareToAppChip(projectionModel)
                         }
                     }
                 }
             }
-            // See b/347726238.
-            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden)
+            // See b/347726238 for [SharingStarted.Lazily] reasoning.
+            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden())
+
+    private val chipTransitionHelper = ChipTransitionHelper(scope)
+
+    override val chip: StateFlow<OngoingActivityChipModel> =
+        chipTransitionHelper.createChipFlow(internalChip)
+
+    /**
+     * Notifies this class that the user just stopped a screen recording from the dialog that's
+     * shown when you tap the recording chip.
+     */
+    fun onRecordingStoppedFromDialog() {
+        // When a screen recording is active, share-to-app is also active (screen recording is just
+        // a special case of share-to-app, where the specific app receiving the share is System UI).
+        // When a screen recording is stopped, we immediately hide the screen recording chip in
+        // [com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.ScreenRecordChipViewModel].
+        // We *also* need to immediately hide the share-to-app chip so it doesn't briefly show.
+        // See b/350891338.
+        chipTransitionHelper.onActivityStoppedFromDialog()
+    }
 
     /** Stops the currently active projection. */
-    private fun stopProjecting() {
-        logger.log(TAG, LogLevel.INFO, {}, { "Stop sharing requested" })
+    private fun stopProjectingFromDialog() {
+        logger.log(TAG, LogLevel.INFO, {}, { "Stop sharing requested from dialog" })
+        chipTransitionHelper.onActivityStoppedFromDialog()
         mediaProjectionChipInteractor.stopProjecting()
     }
 
@@ -92,7 +117,16 @@
             colors = ColorsModel.Red,
             // TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
             startTimeMs = systemClock.elapsedRealtime(),
-            createDialogLaunchOnClickListener(createShareToAppDialogDelegate(state), logger, TAG),
+            createDialogLaunchOnClickListener(
+                createShareToAppDialogDelegate(state),
+                dialogTransitionAnimator,
+                DialogCuj(
+                    Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP,
+                    tag = "Share to app",
+                ),
+                logger,
+                TAG,
+            ),
         )
     }
 
@@ -100,7 +134,7 @@
         EndShareToAppDialogDelegate(
             endMediaProjectionDialogHelper,
             context,
-            stopAction = this::stopProjecting,
+            stopAction = this::stopProjectingFromDialog,
             state,
         )
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index 40f86f9..17cf60b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -24,9 +24,15 @@
     /** Condensed name representing the model, used for logs. */
     abstract val logName: String
 
-    /** This chip shouldn't be shown. */
-    data object Hidden : OngoingActivityChipModel() {
-        override val logName = "Hidden"
+    /**
+     * This chip shouldn't be shown.
+     *
+     * @property shouldAnimate true if the transition from [Shown] to [Hidden] should be animated,
+     *   and false if that transition should *not* be animated (i.e. the chip view should
+     *   immediately disappear).
+     */
+    data class Hidden(val shouldAnimate: Boolean = true) : OngoingActivityChipModel() {
+        override val logName = "Hidden(anim=$shouldAnimate)"
     }
 
     /** This chip should be shown with the given information. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelper.kt
new file mode 100644
index 0000000..92e72c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelper.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 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.chips.ui.viewmodel
+
+import android.annotation.SuppressLint
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transformLatest
+import kotlinx.coroutines.launch
+
+/**
+ * A class that can help [OngoingActivityChipViewModel] instances with various transition states.
+ *
+ * For now, this class's only functionality is immediately hiding the chip if the user has tapped an
+ * activity chip and then clicked "Stop" on the resulting dialog. There's a bit of a delay between
+ * when the user clicks "Stop" and when the system services notify SysUI that the activity has
+ * indeed stopped. We don't want the chip to briefly show for a few frames during that delay, so
+ * this class helps us immediately hide the chip as soon as the user clicks "Stop" in the dialog.
+ * See b/353249803#comment4.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+class ChipTransitionHelper(@Application private val scope: CoroutineScope) {
+    /** A flow that emits each time the user has clicked "Stop" on the dialog. */
+    @SuppressLint("SharedFlowCreation")
+    private val activityStoppedFromDialogEvent = MutableSharedFlow<Unit>()
+
+    /** True if the user recently stopped the activity from the dialog. */
+    private val wasActivityRecentlyStoppedFromDialog: Flow<Boolean> =
+        activityStoppedFromDialogEvent
+            .transformLatest {
+                // Give system services 500ms to stop the activity and notify SysUI. Once more than
+                // 500ms has elapsed, we should go back to using the current system service
+                // information as the source of truth.
+                emit(true)
+                delay(500)
+                emit(false)
+            }
+            // Use stateIn so that the flow created in [createChipFlow] is guaranteed to
+            // emit. (`combine`s require that all input flows have emitted.)
+            .stateIn(scope, SharingStarted.Lazily, false)
+
+    /**
+     * Notifies this class that the user just clicked "Stop" on the stop dialog that's shown when
+     * the chip is tapped.
+     *
+     * Call this method in order to immediately hide the chip.
+     */
+    fun onActivityStoppedFromDialog() {
+        // Because this event causes UI changes, make sure it's launched on the main thread scope.
+        scope.launch { activityStoppedFromDialogEvent.emit(Unit) }
+    }
+
+    /**
+     * Creates a flow that will forcibly hide the chip if the user recently stopped the activity
+     * (see [onActivityStoppedFromDialog]). In general, this flow just uses value in [chip].
+     */
+    fun createChipFlow(chip: Flow<OngoingActivityChipModel>): StateFlow<OngoingActivityChipModel> {
+        return combine(
+                chip,
+                wasActivityRecentlyStoppedFromDialog,
+            ) { chipModel, activityRecentlyStopped ->
+                if (activityRecentlyStopped) {
+                    // There's a bit of a delay between when the user stops an activity via
+                    // SysUI and when the system services notify SysUI that the activity has
+                    // indeed stopped. Prevent the chip from showing during this delay by
+                    // immediately hiding it without any animation.
+                    OngoingActivityChipModel.Hidden(shouldAnimate = false)
+                } else {
+                    chipModel
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden())
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
index ee010f7..2fc366b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
@@ -17,10 +17,14 @@
 package com.android.systemui.statusbar.chips.ui.viewmodel
 
 import android.view.View
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.chips.StatusBarChipsLog
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import kotlinx.coroutines.flow.StateFlow
 
@@ -36,13 +40,19 @@
         /** Creates a chip click listener that launches a dialog created by [dialogDelegate]. */
         fun createDialogLaunchOnClickListener(
             dialogDelegate: SystemUIDialog.Delegate,
+            dialogTransitionAnimator: DialogTransitionAnimator,
+            cuj: DialogCuj,
             @StatusBarChipsLog logger: LogBuffer,
             tag: String,
         ): View.OnClickListener {
-            return View.OnClickListener { _ ->
+            return View.OnClickListener { view ->
                 logger.log(tag, LogLevel.INFO, {}, { "Chip clicked" })
                 val dialog = dialogDelegate.createDialog()
-                dialog.show()
+                val launchableView =
+                    view.requireViewById<ChipBackgroundContainer>(
+                        R.id.ongoing_activity_chip_background
+                    )
+                dialogTransitionAnimator.showFromView(dialog, launchableView, cuj)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
index 15c348e..b0d897d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
@@ -26,11 +26,14 @@
 import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.ScreenRecordChipViewModel
 import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.util.kotlin.pairwise
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
 /**
@@ -50,49 +53,132 @@
     callChipViewModel: CallChipViewModel,
     @StatusBarChipsLog private val logger: LogBuffer,
 ) {
+    private enum class ChipType {
+        ScreenRecord,
+        ShareToApp,
+        CastToOtherDevice,
+        Call,
+    }
+
+    /** Model that helps us internally track the various chip states from each of the types. */
+    private sealed interface InternalChipModel {
+        /**
+         * Represents that we've internally decided to show the chip with type [type] with the given
+         * [model] information.
+         */
+        data class Shown(val type: ChipType, val model: OngoingActivityChipModel.Shown) :
+            InternalChipModel
+
+        /**
+         * Represents that all chip types would like to be hidden. Each value specifies *how* that
+         * chip type should get hidden.
+         */
+        data class Hidden(
+            val screenRecord: OngoingActivityChipModel.Hidden,
+            val shareToApp: OngoingActivityChipModel.Hidden,
+            val castToOtherDevice: OngoingActivityChipModel.Hidden,
+            val call: OngoingActivityChipModel.Hidden,
+        ) : InternalChipModel
+    }
+
+    private val internalChip: Flow<InternalChipModel> =
+        combine(
+            screenRecordChipViewModel.chip,
+            shareToAppChipViewModel.chip,
+            castToOtherDeviceChipViewModel.chip,
+            callChipViewModel.chip,
+        ) { screenRecord, shareToApp, castToOtherDevice, call ->
+            logger.log(
+                TAG,
+                LogLevel.INFO,
+                {
+                    str1 = screenRecord.logName
+                    str2 = shareToApp.logName
+                    str3 = castToOtherDevice.logName
+                },
+                { "Chips: ScreenRecord=$str1 > ShareToApp=$str2 > CastToOther=$str3..." },
+            )
+            logger.log(TAG, LogLevel.INFO, { str1 = call.logName }, { "... > Call=$str1" })
+            // This `when` statement shows the priority order of the chips.
+            when {
+                // Screen recording also activates the media projection APIs, so whenever the
+                // screen recording chip is active, the media projection chip would also be
+                // active. We want the screen-recording-specific chip shown in this case, so we
+                // give the screen recording chip priority. See b/296461748.
+                screenRecord is OngoingActivityChipModel.Shown ->
+                    InternalChipModel.Shown(ChipType.ScreenRecord, screenRecord)
+                shareToApp is OngoingActivityChipModel.Shown ->
+                    InternalChipModel.Shown(ChipType.ShareToApp, shareToApp)
+                castToOtherDevice is OngoingActivityChipModel.Shown ->
+                    InternalChipModel.Shown(ChipType.CastToOtherDevice, castToOtherDevice)
+                call is OngoingActivityChipModel.Shown ->
+                    InternalChipModel.Shown(ChipType.Call, call)
+                else -> {
+                    // We should only get here if all chip types are hidden
+                    check(screenRecord is OngoingActivityChipModel.Hidden)
+                    check(shareToApp is OngoingActivityChipModel.Hidden)
+                    check(castToOtherDevice is OngoingActivityChipModel.Hidden)
+                    check(call is OngoingActivityChipModel.Hidden)
+                    InternalChipModel.Hidden(
+                        screenRecord = screenRecord,
+                        shareToApp = shareToApp,
+                        castToOtherDevice = castToOtherDevice,
+                        call = call,
+                    )
+                }
+            }
+        }
+
     /**
      * A flow modeling the chip that should be shown in the status bar after accounting for possibly
-     * multiple ongoing activities.
+     * multiple ongoing activities and animation requirements.
      *
      * [com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment] is responsible for
      * actually displaying the chip.
      */
     val chip: StateFlow<OngoingActivityChipModel> =
-        combine(
-                screenRecordChipViewModel.chip,
-                shareToAppChipViewModel.chip,
-                castToOtherDeviceChipViewModel.chip,
-                callChipViewModel.chip,
-            ) { screenRecord, shareToApp, castToOtherDevice, call ->
-                logger.log(
-                    TAG,
-                    LogLevel.INFO,
-                    {
-                        str1 = screenRecord.logName
-                        str2 = shareToApp.logName
-                        str3 = castToOtherDevice.logName
-                    },
-                    { "Chips: ScreenRecord=$str1 > ShareToApp=$str2 > CastToOther=$str3..." },
-                )
-                logger.log(TAG, LogLevel.INFO, { str1 = call.logName }, { "... > Call=$str1" })
-                // This `when` statement shows the priority order of the chips
-                when {
-                    // Screen recording also activates the media projection APIs, so whenever the
-                    // screen recording chip is active, the media projection chip would also be
-                    // active. We want the screen-recording-specific chip shown in this case, so we
-                    // give the screen recording chip priority. See b/296461748.
-                    screenRecord is OngoingActivityChipModel.Shown -> screenRecord
-                    shareToApp is OngoingActivityChipModel.Shown -> shareToApp
-                    castToOtherDevice is OngoingActivityChipModel.Shown -> castToOtherDevice
-                    else -> call
+        internalChip
+            .pairwise(initialValue = DEFAULT_INTERNAL_HIDDEN_MODEL)
+            .map { (old, new) ->
+                if (old is InternalChipModel.Shown && new is InternalChipModel.Hidden) {
+                    // If we're transitioning from showing the chip to hiding the chip, different
+                    // chips require different animation behaviors. For example, the screen share
+                    // chips shouldn't animate if the user stopped the screen share from the dialog
+                    // (see b/353249803#comment4), but the call chip should always animate.
+                    //
+                    // This `when` block makes sure that when we're transitioning from Shown to
+                    // Hidden, we check what chip type was previously showing and we use that chip
+                    // type's hide animation behavior.
+                    when (old.type) {
+                        ChipType.ScreenRecord -> new.screenRecord
+                        ChipType.ShareToApp -> new.shareToApp
+                        ChipType.CastToOtherDevice -> new.castToOtherDevice
+                        ChipType.Call -> new.call
+                    }
+                } else if (new is InternalChipModel.Shown) {
+                    // If we have a chip to show, always show it.
+                    new.model
+                } else {
+                    // In the Hidden -> Hidden transition, it shouldn't matter which hidden model we
+                    // choose because no animation should happen regardless.
+                    OngoingActivityChipModel.Hidden()
                 }
             }
             // Some of the chips could have timers in them and we don't want the start time
             // for those timers to get reset for any reason. So, as soon as any subscriber has
-            // requested the chip information, we need to maintain it forever. See b/347726238.
-            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden)
+            // requested the chip information, we maintain it forever by using
+            // [SharingStarted.Lazily]. See b/347726238.
+            .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden())
 
     companion object {
         private const val TAG = "ChipsViewModel"
+
+        private val DEFAULT_INTERNAL_HIDDEN_MODEL =
+            InternalChipModel.Hidden(
+                screenRecord = OngoingActivityChipModel.Hidden(),
+                shareToApp = OngoingActivityChipModel.Hidden(),
+                castToOtherDevice = OngoingActivityChipModel.Hidden(),
+                call = OngoingActivityChipModel.Hidden(),
+            )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index aced0be..0320a7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -528,9 +528,10 @@
                 }
 
                 @Override
-                public void onOngoingActivityStatusChanged(boolean hasOngoingActivity) {
+                public void onOngoingActivityStatusChanged(
+                        boolean hasOngoingActivity, boolean shouldAnimate) {
                     mHasOngoingActivity = hasOngoingActivity;
-                    updateStatusBarVisibilities(/* animate= */ true);
+                    updateStatusBarVisibilities(shouldAnimate);
                 }
 
                 @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
index ae1898b..4c97854 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
@@ -122,7 +122,8 @@
 
                                     // Notify listeners
                                     listener.onOngoingActivityStatusChanged(
-                                        hasOngoingActivity = true
+                                        hasOngoingActivity = true,
+                                        shouldAnimate = true,
                                     )
                                 }
                                 is OngoingActivityChipModel.Hidden -> {
@@ -130,7 +131,8 @@
                                     // b/192243808 and [Chronometer.start].
                                     chipTimeView.stop()
                                     listener.onOngoingActivityStatusChanged(
-                                        hasOngoingActivity = false
+                                        hasOngoingActivity = false,
+                                        shouldAnimate = chipModel.shouldAnimate,
                                     )
                                 }
                             }
@@ -266,8 +268,13 @@
     /** Called when a transition from lockscreen to dream has started. */
     fun onTransitionFromLockscreenToDreamStarted()
 
-    /** Called when the status of the ongoing activity chip (active or not active) has changed. */
-    fun onOngoingActivityStatusChanged(hasOngoingActivity: Boolean)
+    /**
+     * Called when the status of the ongoing activity chip (active or not active) has changed.
+     *
+     * @param shouldAnimate true if the chip should animate in/out, and false if the chip should
+     *   immediately appear/disappear.
+     */
+    fun onOngoingActivityStatusChanged(hasOngoingActivity: Boolean, shouldAnimate: Boolean)
 
     /**
      * Called when the scene state has changed such that the home status bar is newly allowed or no
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
index c9a7c82..02764f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
@@ -16,9 +16,13 @@
 
 package com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel
 
+import android.content.DialogInterface
 import android.view.View
 import androidx.test.filters.SmallTest
+import com.android.internal.jank.Cuj
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
@@ -37,6 +41,8 @@
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
 import com.android.systemui.statusbar.policy.CastDevice
@@ -45,7 +51,10 @@
 import kotlin.test.Test
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
@@ -60,6 +69,16 @@
 
     private val mockScreenCastDialog = mock<SystemUIDialog>()
     private val mockGenericCastDialog = mock<SystemUIDialog>()
+    private val chipBackgroundView = mock<ChipBackgroundContainer>()
+    private val chipView =
+        mock<View>().apply {
+            whenever(
+                    this.requireViewById<ChipBackgroundContainer>(
+                        R.id.ongoing_activity_chip_background
+                    )
+                )
+                .thenReturn(chipBackgroundView)
+        }
 
     private val underTest = kosmos.castToOtherDeviceChipViewModel
 
@@ -193,6 +212,63 @@
         }
 
     @Test
+    fun chip_projectionStoppedFromDialog_chipImmediatelyHidden() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+
+            // WHEN the stop action on the dialog is clicked
+            val dialogStopAction =
+                getStopActionFromDialog(latest, chipView, mockScreenCastDialog, kosmos)
+            dialogStopAction.onClick(mock<DialogInterface>(), 0)
+
+            // THEN the chip is immediately hidden...
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+            // ...even though the repo still says it's projecting
+            assertThat(mediaProjectionRepo.mediaProjectionState.value)
+                .isInstanceOf(MediaProjectionState.Projecting::class.java)
+
+            // AND we specify no animation
+            assertThat((latest as OngoingActivityChipModel.Hidden).shouldAnimate).isFalse()
+        }
+
+    @Test
+    fun chip_routeStoppedFromDialog_chipImmediatelyHidden() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            mediaRouterRepo.castDevices.value =
+                listOf(
+                    CastDevice(
+                        state = CastDevice.CastState.Connected,
+                        id = "id",
+                        name = "name",
+                        description = "desc",
+                        origin = CastDevice.CastOrigin.MediaRouter,
+                    )
+                )
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+
+            // WHEN the stop action on the dialog is clicked
+            val dialogStopAction =
+                getStopActionFromDialog(latest, chipView, mockGenericCastDialog, kosmos)
+            dialogStopAction.onClick(mock<DialogInterface>(), 0)
+
+            // THEN the chip is immediately hidden...
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+            // ...even though the repo still says it's projecting
+            assertThat(mediaRouterRepo.castDevices.value).isNotEmpty()
+
+            // AND we specify no animation
+            assertThat((latest as OngoingActivityChipModel.Hidden).shouldAnimate).isFalse()
+        }
+
+    @Test
     fun chip_colorsAreRed() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
@@ -297,8 +373,14 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(mock<View>())
-            verify(mockScreenCastDialog).show()
+            clickListener!!.onClick(chipView)
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    eq(mockScreenCastDialog),
+                    eq(chipBackgroundView),
+                    any(),
+                    anyBoolean(),
+                )
         }
 
     @Test
@@ -316,8 +398,14 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(mock<View>())
-            verify(mockScreenCastDialog).show()
+            clickListener!!.onClick(chipView)
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    eq(mockScreenCastDialog),
+                    eq(chipBackgroundView),
+                    any(),
+                    anyBoolean(),
+                )
         }
 
     @Test
@@ -339,7 +427,70 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(mock<View>())
-            verify(mockGenericCastDialog).show()
+            clickListener!!.onClick(chipView)
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    eq(mockGenericCastDialog),
+                    eq(chipBackgroundView),
+                    any(),
+                    anyBoolean(),
+                )
+        }
+
+    @Test
+    fun chip_projectionStateCasting_clickListenerHasCuj() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
+
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            clickListener!!.onClick(chipView)
+
+            val cujCaptor = argumentCaptor<DialogCuj>()
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    any(),
+                    any(),
+                    cujCaptor.capture(),
+                    anyBoolean(),
+                )
+
+            assertThat(cujCaptor.firstValue.cujType)
+                .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP)
+            assertThat(cujCaptor.firstValue.tag).contains("Cast")
+        }
+
+    @Test
+    fun chip_routerStateCasting_clickListenerHasCuj() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            mediaRouterRepo.castDevices.value =
+                listOf(
+                    CastDevice(
+                        state = CastDevice.CastState.Connected,
+                        id = "id",
+                        name = "name",
+                        description = "desc",
+                        origin = CastDevice.CastOrigin.MediaRouter,
+                    )
+                )
+
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            clickListener!!.onClick(chipView)
+
+            val cujCaptor = argumentCaptor<DialogCuj>()
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    any(),
+                    any(),
+                    cujCaptor.capture(),
+                    anyBoolean(),
+                )
+
+            assertThat(cujCaptor.firstValue.cujType)
+                .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP)
+            assertThat(cujCaptor.firstValue.tag).contains("Cast")
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
index 4728c64..b4a37ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
@@ -16,9 +16,13 @@
 
 package com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel
 
+import android.content.DialogInterface
 import android.view.View
 import androidx.test.filters.SmallTest
+import com.android.internal.jank.Cuj
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
@@ -30,9 +34,13 @@
 import com.android.systemui.res.R
 import com.android.systemui.screenrecord.data.model.ScreenRecordModel
 import com.android.systemui.screenrecord.data.repository.screenRecordRepository
+import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
 import com.android.systemui.statusbar.chips.screenrecord.ui.view.EndScreenRecordingDialogDelegate
+import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
 import com.android.systemui.util.time.fakeSystemClock
@@ -40,7 +48,10 @@
 import kotlin.test.Test
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
@@ -53,11 +64,22 @@
     private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository
     private val systemClock = kosmos.fakeSystemClock
     private val mockSystemUIDialog = mock<SystemUIDialog>()
+    private val chipBackgroundView = mock<ChipBackgroundContainer>()
+    private val chipView =
+        mock<View>().apply {
+            whenever(
+                    this.requireViewById<ChipBackgroundContainer>(
+                        R.id.ongoing_activity_chip_background
+                    )
+                )
+                .thenReturn(chipBackgroundView)
+        }
 
     private val underTest = kosmos.screenRecordChipViewModel
 
     @Before
     fun setUp() {
+        setUpPackageManagerForMediaProjection(kosmos)
         whenever(kosmos.mockSystemUIDialogFactory.create(any<EndScreenRecordingDialogDelegate>()))
             .thenReturn(mockSystemUIDialog)
     }
@@ -132,6 +154,40 @@
         }
 
     @Test
+    fun chip_recordingStoppedFromDialog_screenRecordAndShareToAppChipImmediatelyHidden() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+            val latestShareToApp by collectLastValue(kosmos.shareToAppChipViewModel.chip)
+
+            // On real devices, when screen recording is active then share-to-app is also active
+            // because screen record is just a special case of share-to-app where the app receiving
+            // the share is SysUI
+            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen("fake.package")
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+            assertThat(latestShareToApp).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+
+            // WHEN the stop action on the dialog is clicked
+            val dialogStopAction =
+                getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos)
+            dialogStopAction.onClick(mock<DialogInterface>(), 0)
+
+            // THEN both the screen record chip and the share-to-app chip are immediately hidden...
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+            assertThat(latestShareToApp).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+            // ...even though the repos still say it's recording
+            assertThat(screenRecordRepo.screenRecordState.value)
+                .isEqualTo(ScreenRecordModel.Recording)
+            assertThat(mediaProjectionRepo.mediaProjectionState.value)
+                .isInstanceOf(MediaProjectionState.Projecting::class.java)
+
+            // AND we specify no animation
+            assertThat((latest as OngoingActivityChipModel.Hidden).shouldAnimate).isFalse()
+        }
+
+    @Test
     fun chip_startingState_colorsAreRed() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
@@ -182,9 +238,15 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(mock<View>())
+            clickListener!!.onClick(chipView)
             // EndScreenRecordingDialogDelegate will test that the dialog has the right message
-            verify(mockSystemUIDialog).show()
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    eq(mockSystemUIDialog),
+                    eq(chipBackgroundView),
+                    any(),
+                    anyBoolean(),
+                )
         }
 
     @Test
@@ -198,9 +260,15 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(mock<View>())
+            clickListener!!.onClick(chipView)
             // EndScreenRecordingDialogDelegate will test that the dialog has the right message
-            verify(mockSystemUIDialog).show()
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    eq(mockSystemUIDialog),
+                    eq(chipBackgroundView),
+                    any(),
+                    anyBoolean(),
+                )
         }
 
     @Test
@@ -218,8 +286,39 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(mock<View>())
+            clickListener!!.onClick(chipView)
             // EndScreenRecordingDialogDelegate will test that the dialog has the right message
-            verify(mockSystemUIDialog).show()
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    eq(mockSystemUIDialog),
+                    eq(chipBackgroundView),
+                    any(),
+                    anyBoolean(),
+                )
+        }
+
+    @Test
+    fun chip_clickListenerHasCuj() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen("host.package")
+
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            clickListener!!.onClick(chipView)
+
+            val cujCaptor = argumentCaptor<DialogCuj>()
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    any(),
+                    any(),
+                    cujCaptor.capture(),
+                    anyBoolean(),
+                )
+
+            assertThat(cujCaptor.firstValue.cujType)
+                .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP)
+            assertThat(cujCaptor.firstValue.tag).contains("Screen record")
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
index f87b17d..2658679 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
@@ -16,9 +16,13 @@
 
 package com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel
 
+import android.content.DialogInterface
 import android.view.View
 import androidx.test.filters.SmallTest
+import com.android.internal.jank.Cuj
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
@@ -34,6 +38,8 @@
 import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndShareToAppDialogDelegate
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
 import com.android.systemui.util.time.fakeSystemClock
@@ -41,7 +47,10 @@
 import kotlin.test.Test
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
@@ -54,6 +63,16 @@
     private val systemClock = kosmos.fakeSystemClock
 
     private val mockShareDialog = mock<SystemUIDialog>()
+    private val chipBackgroundView = mock<ChipBackgroundContainer>()
+    private val chipView =
+        mock<View>().apply {
+            whenever(
+                    this.requireViewById<ChipBackgroundContainer>(
+                        R.id.ongoing_activity_chip_background
+                    )
+                )
+                .thenReturn(chipBackgroundView)
+        }
 
     private val underTest = kosmos.shareToAppChipViewModel
 
@@ -134,6 +153,31 @@
         }
 
     @Test
+    fun chip_shareStoppedFromDialog_chipImmediatelyHidden() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+
+            // WHEN the stop action on the dialog is clicked
+            val dialogStopAction =
+                getStopActionFromDialog(latest, chipView, mockShareDialog, kosmos)
+            dialogStopAction.onClick(mock<DialogInterface>(), 0)
+
+            // THEN the chip is immediately hidden...
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+            // ...even though the repo still says it's projecting
+            assertThat(mediaProjectionRepo.mediaProjectionState.value)
+                .isInstanceOf(MediaProjectionState.Projecting::class.java)
+
+            // AND we specify no animation
+            assertThat((latest as OngoingActivityChipModel.Hidden).shouldAnimate).isFalse()
+        }
+
+    @Test
     fun chip_colorsAreRed() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
@@ -181,8 +225,14 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(mock<View>())
-            verify(mockShareDialog).show()
+            clickListener!!.onClick(chipView)
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    eq(mockShareDialog),
+                    eq(chipBackgroundView),
+                    any(),
+                    anyBoolean(),
+                )
         }
 
     @Test
@@ -199,7 +249,41 @@
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
             assertThat(clickListener).isNotNull()
 
-            clickListener!!.onClick(mock<View>())
-            verify(mockShareDialog).show()
+            clickListener!!.onClick(chipView)
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    eq(mockShareDialog),
+                    eq(chipBackgroundView),
+                    any(),
+                    anyBoolean(),
+                )
+        }
+
+    @Test
+    fun chip_clickListenerHasCuj() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.SingleTask(
+                    NORMAL_PACKAGE,
+                    hostDeviceName = null,
+                    createTask(taskId = 1),
+                )
+
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            clickListener!!.onClick(chipView)
+
+            val cujCaptor = argumentCaptor<DialogCuj>()
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    any(),
+                    any(),
+                    cujCaptor.capture(),
+                    anyBoolean(),
+                )
+
+            assertThat(cujCaptor.firstValue.cujType)
+                .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP)
+            assertThat(cujCaptor.firstValue.tag).contains("Share")
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
new file mode 100644
index 0000000..b9049e8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2024 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.chips.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.ui.model.ColorsModel
+import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class ChipTransitionHelperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+
+    @Test
+    fun createChipFlow_typicallyFollowsInputFlow() =
+        testScope.runTest {
+            val underTest = ChipTransitionHelper(kosmos.applicationCoroutineScope)
+            val inputChipFlow =
+                MutableStateFlow<OngoingActivityChipModel>(OngoingActivityChipModel.Hidden())
+            val latest by collectLastValue(underTest.createChipFlow(inputChipFlow))
+
+            val newChip =
+                OngoingActivityChipModel.Shown.Timer(
+                    icon = Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+                    colors = ColorsModel.Themed,
+                    startTimeMs = 100L,
+                    onClickListener = null,
+                )
+
+            inputChipFlow.value = newChip
+
+            assertThat(latest).isEqualTo(newChip)
+
+            val newerChip =
+                OngoingActivityChipModel.Shown.IconOnly(
+                    icon = Icon.Resource(R.drawable.ic_hotspot, contentDescription = null),
+                    colors = ColorsModel.Themed,
+                    onClickListener = null,
+                )
+
+            inputChipFlow.value = newerChip
+
+            assertThat(latest).isEqualTo(newerChip)
+        }
+
+    @Test
+    fun activityStopped_chipHiddenWithoutAnimationFor500ms() =
+        testScope.runTest {
+            val underTest = ChipTransitionHelper(kosmos.applicationCoroutineScope)
+            val inputChipFlow =
+                MutableStateFlow<OngoingActivityChipModel>(OngoingActivityChipModel.Hidden())
+            val latest by collectLastValue(underTest.createChipFlow(inputChipFlow))
+
+            val shownChip =
+                OngoingActivityChipModel.Shown.Timer(
+                    icon = Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+                    colors = ColorsModel.Themed,
+                    startTimeMs = 100L,
+                    onClickListener = null,
+                )
+
+            inputChipFlow.value = shownChip
+
+            assertThat(latest).isEqualTo(shownChip)
+
+            // WHEN #onActivityStopped is invoked
+            underTest.onActivityStoppedFromDialog()
+            runCurrent()
+
+            // THEN the chip is hidden and has no animation
+            assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false))
+
+            // WHEN only 250ms have elapsed
+            advanceTimeBy(250)
+
+            // THEN the chip is still hidden
+            assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false))
+
+            // WHEN over 500ms have elapsed
+            advanceTimeBy(251)
+
+            // THEN the chip returns to the original input flow value
+            assertThat(latest).isEqualTo(shownChip)
+        }
+
+    @Test
+    fun activityStopped_stoppedAgainBefore500ms_chipReshownAfterSecond500ms() =
+        testScope.runTest {
+            val underTest = ChipTransitionHelper(kosmos.applicationCoroutineScope)
+            val inputChipFlow =
+                MutableStateFlow<OngoingActivityChipModel>(OngoingActivityChipModel.Hidden())
+            val latest by collectLastValue(underTest.createChipFlow(inputChipFlow))
+
+            val shownChip =
+                OngoingActivityChipModel.Shown.Timer(
+                    icon = Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+                    colors = ColorsModel.Themed,
+                    startTimeMs = 100L,
+                    onClickListener = null,
+                )
+
+            inputChipFlow.value = shownChip
+
+            assertThat(latest).isEqualTo(shownChip)
+
+            // WHEN #onActivityStopped is invoked
+            underTest.onActivityStoppedFromDialog()
+            runCurrent()
+
+            // THEN the chip is hidden and has no animation
+            assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false))
+
+            // WHEN 250ms have elapsed, get another stop event
+            advanceTimeBy(250)
+            underTest.onActivityStoppedFromDialog()
+            runCurrent()
+
+            // THEN the chip is still hidden for another 500ms afterwards
+            assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false))
+            advanceTimeBy(499)
+            assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false))
+            advanceTimeBy(2)
+            assertThat(latest).isEqualTo(shownChip)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
index ca043f1..6e4d886 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
@@ -18,32 +18,58 @@
 
 import android.view.View
 import androidx.test.filters.SmallTest
+import com.android.internal.jank.Cuj
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import kotlin.test.Test
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 @SmallTest
 class OngoingActivityChipViewModelTest : SysuiTestCase() {
     private val mockSystemUIDialog = mock<SystemUIDialog>()
     private val dialogDelegate = SystemUIDialog.Delegate { mockSystemUIDialog }
+    private val dialogTransitionAnimator = mock<DialogTransitionAnimator>()
+
+    private val chipBackgroundView = mock<ChipBackgroundContainer>()
+    private val chipView =
+        mock<View>().apply {
+            whenever(
+                    this.requireViewById<ChipBackgroundContainer>(
+                        R.id.ongoing_activity_chip_background
+                    )
+                )
+                .thenReturn(chipBackgroundView)
+        }
 
     @Test
     fun createDialogLaunchOnClickListener_showsDialogOnClick() {
+        val cuj = DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Test")
         val clickListener =
             createDialogLaunchOnClickListener(
                 dialogDelegate,
+                dialogTransitionAnimator,
+                cuj,
                 logcatLogBuffer("OngoingActivityChipViewModelTest"),
                 "tag",
             )
 
-        // Dialogs must be created on the main thread
-        context.mainExecutor.execute {
-            clickListener.onClick(mock<View>())
-            verify(mockSystemUIDialog).show()
-        }
+        clickListener.onClick(chipView)
+        verify(dialogTransitionAnimator)
+            .showFromView(
+                eq(mockSystemUIDialog),
+                eq(chipBackgroundView),
+                eq(cuj),
+                anyBoolean(),
+            )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index b1a8d0b..ee249f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -16,6 +16,10 @@
 
 package com.android.systemui.statusbar.chips.ui.viewmodel
 
+import android.content.DialogInterface
+import android.content.packageManager
+import android.content.pm.PackageManager
+import android.view.View
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -33,10 +37,14 @@
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
 import com.android.systemui.util.time.fakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.runCurrent
@@ -44,9 +52,17 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
 class OngoingActivityChipsViewModelTest : SysuiTestCase() {
     private val kosmos = Kosmos().also { it.testCase = this }
     private val testScope = kosmos.testScope
@@ -56,6 +72,18 @@
     private val mediaProjectionState = kosmos.fakeMediaProjectionRepository.mediaProjectionState
     private val callRepo = kosmos.ongoingCallRepository
 
+    private val mockSystemUIDialog = mock<SystemUIDialog>()
+    private val chipBackgroundView = mock<ChipBackgroundContainer>()
+    private val chipView =
+        mock<View>().apply {
+            whenever(
+                    this.requireViewById<ChipBackgroundContainer>(
+                        R.id.ongoing_activity_chip_background
+                    )
+                )
+                .thenReturn(chipBackgroundView)
+        }
+
     private val underTest = kosmos.ongoingActivityChipsViewModel
 
     @Before
@@ -72,7 +100,7 @@
 
             val latest by collectLastValue(underTest.chip)
 
-            assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden)
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
         }
 
     @Test
@@ -230,7 +258,81 @@
             job2.cancel()
         }
 
+    @Test
+    fun chip_screenRecordStoppedViaDialog_chipHiddenWithoutAnimation() =
+        testScope.runTest {
+            screenRecordState.value = ScreenRecordModel.Recording
+            mediaProjectionState.value = MediaProjectionState.NotProjecting
+            callRepo.setOngoingCallState(OngoingCallModel.NoCall)
+
+            val latest by collectLastValue(underTest.chip)
+
+            assertIsScreenRecordChip(latest)
+
+            // WHEN screen record gets stopped via dialog
+            val dialogStopAction =
+                getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos)
+            dialogStopAction.onClick(mock<DialogInterface>(), 0)
+
+            // THEN the chip is immediately hidden with no animation
+            assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false))
+        }
+
+    @Test
+    fun chip_projectionStoppedViaDialog_chipHiddenWithoutAnimation() =
+        testScope.runTest {
+            mediaProjectionState.value =
+                MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+            screenRecordState.value = ScreenRecordModel.DoingNothing
+            callRepo.setOngoingCallState(OngoingCallModel.NoCall)
+
+            val latest by collectLastValue(underTest.chip)
+
+            assertIsShareToAppChip(latest)
+
+            // WHEN media projection gets stopped via dialog
+            val dialogStopAction =
+                getStopActionFromDialog(latest, chipView, mockSystemUIDialog, kosmos)
+            dialogStopAction.onClick(mock<DialogInterface>(), 0)
+
+            // THEN the chip is immediately hidden with no animation
+            assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false))
+        }
+
     companion object {
+        /**
+         * Assuming that the click listener in [latest] opens a dialog, this fetches the action
+         * associated with the positive button, which we assume is the "Stop sharing" action.
+         */
+        fun getStopActionFromDialog(
+            latest: OngoingActivityChipModel?,
+            chipView: View,
+            dialog: SystemUIDialog,
+            kosmos: Kosmos
+        ): DialogInterface.OnClickListener {
+            // Capture the action that would get invoked when the user clicks "Stop" on the dialog
+            lateinit var dialogStopAction: DialogInterface.OnClickListener
+            Mockito.doAnswer {
+                    val delegate = it.arguments[0] as SystemUIDialog.Delegate
+                    delegate.beforeCreate(dialog, /* savedInstanceState= */ null)
+
+                    val stopActionCaptor = argumentCaptor<DialogInterface.OnClickListener>()
+                    verify(dialog).setPositiveButton(any(), stopActionCaptor.capture())
+                    dialogStopAction = stopActionCaptor.firstValue
+
+                    return@doAnswer dialog
+                }
+                .whenever(kosmos.mockSystemUIDialogFactory)
+                .create(any<SystemUIDialog.Delegate>())
+            whenever(kosmos.packageManager.getApplicationInfo(eq(NORMAL_PACKAGE), any<Int>()))
+                .thenThrow(PackageManager.NameNotFoundException())
+            // Click the chip so that we open the dialog and we fill in [dialogStopAction]
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            clickListener!!.onClick(chipView)
+
+            return dialogStopAction
+        }
+
         fun assertIsScreenRecordChip(latest: OngoingActivityChipModel?) {
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
             val icon = (latest as OngoingActivityChipModel.Shown).icon
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 01540e7..58ad835 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -536,7 +536,7 @@
 
         // WHEN there's *no* ongoing activity via new callback
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
-                /* hasOngoingActivity= */ false);
+                /* hasOngoingActivity= */ false, /* shouldAnimate= */ false);
 
         // THEN the old callback value is used, so the view is shown
         assertEquals(View.VISIBLE,
@@ -548,7 +548,7 @@
 
         // WHEN there *is* an ongoing activity via new callback
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
-                /* hasOngoingActivity= */ true);
+                /* hasOngoingActivity= */ true, /* shouldAnimate= */ false);
 
         // THEN the old callback value is used, so the view is hidden
         assertEquals(View.GONE,
@@ -565,7 +565,7 @@
         // listener, but I'm unable to get the fragment to get attached so that the binder starts
         // listening to flows.
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
-                /* hasOngoingActivity= */ false);
+                /* hasOngoingActivity= */ false, /* shouldAnimate= */ false);
 
         assertEquals(View.GONE,
                 mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
@@ -577,7 +577,7 @@
         resumeAndGetFragment();
 
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
-                /* hasOngoingActivity= */ true);
+                /* hasOngoingActivity= */ true, /* shouldAnimate= */ false);
 
         assertEquals(View.VISIBLE,
                 mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
@@ -590,7 +590,7 @@
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
-                /* hasOngoingActivity= */ true);
+                /* hasOngoingActivity= */ true, /* shouldAnimate= */ false);
 
         fragment.disable(DEFAULT_DISPLAY,
                 StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
@@ -605,7 +605,7 @@
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
-                /* hasOngoingActivity= */ true);
+                /* hasOngoingActivity= */ true, /* shouldAnimate= */ false);
         when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true);
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
@@ -621,14 +621,14 @@
 
         // Ongoing activity started
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
-                /* hasOngoingActivity= */ true);
+                /* hasOngoingActivity= */ true, /* shouldAnimate= */ false);
 
         assertEquals(View.VISIBLE,
                 mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
 
         // Ongoing activity ended
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
-                /* hasOngoingActivity= */ false);
+                /* hasOngoingActivity= */ false, /* shouldAnimate= */ false);
 
         assertEquals(View.GONE,
                 mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
@@ -643,7 +643,7 @@
 
         // Ongoing call started
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
-                /* hasOngoingActivity= */ true);
+                /* hasOngoingActivity= */ true, /* shouldAnimate= */ false);
 
         // Notification area is hidden without delay
         assertEquals(0f, getNotificationAreaView().getAlpha(), 0.01);
@@ -661,7 +661,7 @@
 
         // WHEN there's *no* ongoing activity via new callback
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
-                /* hasOngoingActivity= */ false);
+                /* hasOngoingActivity= */ false, /* shouldAnimate= */ false);
 
         // THEN the new callback value is used, so the view is hidden
         assertEquals(View.GONE,
@@ -673,7 +673,7 @@
 
         // WHEN there *is* an ongoing activity via new callback
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
-                /* hasOngoingActivity= */ true);
+                /* hasOngoingActivity= */ true, /* shouldAnimate= */ false);
 
         // THEN the new callback value is used, so the view is shown
         assertEquals(View.VISIBLE,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
index 94159bc..60750cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
@@ -425,7 +425,7 @@
 
             kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.DoingNothing
 
-            assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden)
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
 
             kosmos.fakeMediaProjectionRepository.mediaProjectionState.value =
                 MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
index d3f1125..cefdf7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
@@ -29,7 +29,7 @@
     override val transitionFromLockscreenToDreamStartedEvent = MutableSharedFlow<Unit>()
 
     override val ongoingActivityChip: MutableStateFlow<OngoingActivityChipModel> =
-        MutableStateFlow(OngoingActivityChipModel.Hidden)
+        MutableStateFlow(OngoingActivityChipModel.Hidden())
 
     override val isHomeStatusBarAllowedByScene = MutableStateFlow(false)
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
index 3d85a4a..c7dfd5c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel
 
 import android.content.applicationContext
+import com.android.systemui.animation.dialogTransitionAnimator
+import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor.mediaRouterChipInteractor
@@ -34,6 +36,7 @@
             mediaRouterChipInteractor = mediaRouterChipInteractor,
             systemClock = fakeSystemClock,
             endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
+            dialogTransitionAnimator = mockDialogTransitionAnimator,
             logger = statusBarChipsLogger,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt
index 1ed7a47..651a0f7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.chips.mediaprojection.ui.view
 
 import android.content.packageManager
+import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
 
@@ -24,6 +25,7 @@
     Kosmos.Fixture {
         EndMediaProjectionDialogHelper(
             dialogFactory = mockSystemUIDialogFactory,
+            dialogTransitionAnimator = mockDialogTransitionAnimator,
             packageManager = packageManager,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
index e4bb166..c2a6f7d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
@@ -17,10 +17,12 @@
 package com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel
 
 import android.content.applicationContext
+import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper
 import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.screenRecordChipInteractor
+import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel
 import com.android.systemui.statusbar.chips.statusBarChipsLogger
 import com.android.systemui.util.time.fakeSystemClock
 
@@ -30,7 +32,9 @@
             scope = applicationCoroutineScope,
             context = applicationContext,
             interactor = screenRecordChipInteractor,
+            shareToAppChipViewModel = shareToAppChipViewModel,
             endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
+            dialogTransitionAnimator = mockDialogTransitionAnimator,
             systemClock = fakeSystemClock,
             logger = statusBarChipsLogger,
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
index 8ed7f96..0770009 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel
 
 import android.content.applicationContext
+import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.mediaProjectionChipInteractor
@@ -32,6 +33,7 @@
             mediaProjectionChipInteractor = mediaProjectionChipInteractor,
             systemClock = fakeSystemClock,
             endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
+            dialogTransitionAnimator = mockDialogTransitionAnimator,
             logger = statusBarChipsLogger,
         )
     }