[Screen share] Update our status to "projecting" earlier in process.

In the old screen share icon, we started showing the icon as soon as
MediaProjectionManager.Callback#onStart is called. With the new screen
share chips, we only started showing the icon when
MediaProjectionManager.Callback#onRecordingSessionSet is called. If a
user is only sharing *audio* to an app, but not their screen,
the #onRecordingSessionSet method is never triggered but we should still
show the screen share chip.

Bug: 373308507
Bug: 332662551
Flag: com.android.systemui.status_bar_show_audio_only_projection_chip

Regression tests:
Test: Screen record single app -> verify chip does 3-2-1 countdown then
starts timer, has screen record icon
Test: Screen record full screen -> verify chip does 3-2-1 countdown then
starts timer, has screen record icon
Test: Share screen to app that does a 3-2-1 countdown -> verify chip
starts showing with just share-to-app icon, then shows timer after 3-2-1
countdown is done
Test: Share screen to app that immediately starts recording -> verify
chip timer immediately starts
Test: Cast screen to another device -> verify chip shows cast icon +
timer
Test: Cast audio to another device -> verify chip just shows cast icon

Test: atest MediaProjectionManagerRepositoryTest

New test:
Test: Share *audio* to another app -> verify chip shows, and has
share-to-app icon

Change-Id: Ifa84ec811ef3456792a3cd6b7766d6c738536af2
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index a21a805..bd872eb 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -338,6 +338,17 @@
 }
 
 flag {
+    name: "status_bar_show_audio_only_projection_chip"
+    namespace: "systemui"
+    description: "Show chip on the left side of the status bar when a user is only sharing *audio* "
+        "during a media projection"
+    bug: "373308507"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "status_bar_use_repos_for_call_chip"
     namespace: "systemui"
     description: "Use repositories as the source of truth for call notifications shown as a chip in"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
index 785d5a8..02825a5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -21,10 +21,13 @@
 import android.os.Binder
 import android.os.Handler
 import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.view.ContentRecordingSession
 import android.view.Display
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -74,7 +77,8 @@
         }
 
     @Test
-    fun mediaProjectionState_onStart_emitsNotProjecting() =
+    @DisableFlags(FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP)
+    fun mediaProjectionState_onStart_flagOff_emitsNotProjecting() =
         testScope.runTest {
             val state by collectLastValue(repo.mediaProjectionState)
 
@@ -84,6 +88,35 @@
         }
 
     @Test
+    @EnableFlags(FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP)
+    fun mediaProjectionState_onStart_flagOn_emitsProjectingNoScreen() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+
+            fakeMediaProjectionManager.dispatchOnStart()
+
+            assertThat(state).isInstanceOf(MediaProjectionState.Projecting.NoScreen::class.java)
+        }
+
+    @Test
+    @EnableFlags(FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP)
+    fun mediaProjectionState_noScreen_hasHostPackage() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+
+            val info =
+                MediaProjectionInfo(
+                    /* packageName= */ "com.media.projection.repository.test",
+                    /* handle= */ UserHandle.getUserHandleForUid(UserHandle.myUserId()),
+                    /* launchCookie = */ null,
+                )
+            fakeMediaProjectionManager.dispatchOnStart(info)
+
+            assertThat((state as MediaProjectionState.Projecting).hostPackage)
+                .isEqualTo("com.media.projection.repository.test")
+        }
+
+    @Test
     fun mediaProjectionState_onStop_emitsNotProjecting() =
         testScope.runTest {
             val state by collectLastValue(repo.mediaProjectionState)
@@ -212,7 +245,7 @@
                 )
             fakeMediaProjectionManager.dispatchOnSessionSet(
                 info = info,
-                session = ContentRecordingSession.createTaskSession(token.asBinder())
+                session = ContentRecordingSession.createTaskSession(token.asBinder()),
             )
 
             assertThat((state as MediaProjectionState.Projecting.SingleTask).hostPackage)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
index 5005d16..e33ce9c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
@@ -92,7 +92,7 @@
         createAndSetDelegate(
             MediaProjectionState.Projecting.EntireScreen(
                 HOST_PACKAGE,
-                hostDeviceName = "My Favorite Device"
+                hostDeviceName = "My Favorite Device",
             )
         )
 
@@ -118,8 +118,8 @@
             MediaProjectionState.Projecting.SingleTask(
                 HOST_PACKAGE,
                 hostDeviceName = null,
-                createTask(taskId = 1, baseIntent = baseIntent)
-            ),
+                createTask(taskId = 1, baseIntent = baseIntent),
+            )
         )
 
         underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
@@ -141,8 +141,8 @@
             MediaProjectionState.Projecting.SingleTask(
                 HOST_PACKAGE,
                 hostDeviceName = "My Favorite Device",
-                createTask(taskId = 1, baseIntent = baseIntent)
-            ),
+                createTask(taskId = 1, baseIntent = baseIntent),
+            )
         )
 
         underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
@@ -169,8 +169,8 @@
             MediaProjectionState.Projecting.SingleTask(
                 HOST_PACKAGE,
                 hostDeviceName = null,
-                createTask(taskId = 1, baseIntent = baseIntent)
-            ),
+                createTask(taskId = 1, baseIntent = baseIntent),
+            )
         )
 
         underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
@@ -198,7 +198,7 @@
                 HOST_PACKAGE,
                 hostDeviceName = "My Favorite Device",
                 createTask(taskId = 1, baseIntent = baseIntent),
-            ),
+            )
         )
 
         underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
@@ -235,7 +235,7 @@
             verify(sysuiDialog)
                 .setPositiveButton(
                     eq(R.string.cast_to_other_device_stop_dialog_button),
-                    clickListener.capture()
+                    clickListener.capture(),
                 )
 
             // Verify that clicking the button stops the recording
@@ -254,7 +254,8 @@
                 kosmos.applicationContext,
                 stopAction = kosmos.mediaProjectionChipInteractor::stopProjecting,
                 ProjectionChipModel.Projecting(
-                    ProjectionChipModel.Type.CAST_TO_OTHER_DEVICE,
+                    ProjectionChipModel.Receiver.CastToOtherDevice,
+                    ProjectionChipModel.ContentType.Screen,
                     state,
                 ),
             )
@@ -268,7 +269,7 @@
             MediaProjectionState.Projecting.SingleTask(
                 HOST_PACKAGE,
                 hostDeviceName = null,
-                createTask(taskId = 1)
+                createTask(taskId = 1),
             )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
index 77992db..01e5501 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
@@ -17,9 +17,11 @@
 package com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel
 
 import android.content.DialogInterface
+import android.platform.test.annotations.EnableFlags
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.internal.jank.Cuj
+import com.android.systemui.Flags.FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.mockDialogTransitionAnimator
@@ -135,6 +137,29 @@
         }
 
     @Test
+    @EnableFlags(FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP)
+    fun chip_projectionIsAudioOnly_otherDevicePackage_isShownAsIconOnly() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+            mediaRouterRepo.castDevices.value = emptyList()
+
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.NoScreen(
+                    hostPackage = CAST_TO_OTHER_DEVICES_PACKAGE
+                )
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
+            val icon =
+                (((latest as OngoingActivityChipModel.Shown).icon)
+                        as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
+                    .impl as Icon.Resource
+            assertThat(icon.res).isEqualTo(R.drawable.ic_cast_connected)
+            // This content description is just generic "Casting", not "Casting screen"
+            assertThat((icon.contentDescription as ContentDescription.Resource).res)
+                .isEqualTo(R.string.accessibility_casting)
+        }
+
+    @Test
     fun chip_projectionIsEntireScreenState_otherDevicesPackage_isShownAsTimer_forScreen() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
@@ -292,6 +317,18 @@
         }
 
     @Test
+    @EnableFlags(FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP)
+    fun chip_projectionIsNoScreenState_normalPackage_isHidden() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.NoScreen(NORMAL_PACKAGE)
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+        }
+
+    @Test
     fun chip_projectionIsSingleTaskState_normalPackage_isHidden() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
@@ -387,12 +424,7 @@
 
             clickListener!!.onClick(chipView)
             verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    eq(mockScreenCastDialog),
-                    eq(chipBackgroundView),
-                    any(),
-                    anyBoolean(),
-                )
+                .showFromView(eq(mockScreenCastDialog), eq(chipBackgroundView), any(), anyBoolean())
         }
 
     @Test
@@ -412,12 +444,7 @@
 
             clickListener!!.onClick(chipView)
             verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    eq(mockScreenCastDialog),
-                    eq(chipBackgroundView),
-                    any(),
-                    anyBoolean(),
-                )
+                .showFromView(eq(mockScreenCastDialog), eq(chipBackgroundView), any(), anyBoolean())
         }
 
     @Test
@@ -461,12 +488,7 @@
 
             val cujCaptor = argumentCaptor<DialogCuj>()
             verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    any(),
-                    any(),
-                    cujCaptor.capture(),
-                    anyBoolean(),
-                )
+                .showFromView(any(), any(), cujCaptor.capture(), anyBoolean())
 
             assertThat(cujCaptor.firstValue.cujType)
                 .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP)
@@ -494,12 +516,7 @@
 
             val cujCaptor = argumentCaptor<DialogCuj>()
             verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    any(),
-                    any(),
-                    cujCaptor.capture(),
-                    anyBoolean(),
-                )
+                .showFromView(any(), any(), cujCaptor.capture(), anyBoolean())
 
             assertThat(cujCaptor.firstValue.cujType)
                 .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
index d0c5e7a..611318a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
@@ -21,7 +21,9 @@
 import android.content.packageManager
 import android.content.pm.PackageManager
 import android.content.pm.ResolveInfo
+import android.platform.test.annotations.EnableFlags
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
@@ -65,7 +67,23 @@
         }
 
     @Test
-    fun projection_singleTaskState_otherDevicesPackage_isCastToOtherDeviceType() =
+    @EnableFlags(FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP)
+    fun projection_noScreenState_otherDevicesPackage_isCastToOtherAndAudio() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.projection)
+
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.NoScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
+
+            assertThat(latest).isInstanceOf(ProjectionChipModel.Projecting::class.java)
+            assertThat((latest as ProjectionChipModel.Projecting).receiver)
+                .isEqualTo(ProjectionChipModel.Receiver.CastToOtherDevice)
+            assertThat((latest as ProjectionChipModel.Projecting).contentType)
+                .isEqualTo(ProjectionChipModel.ContentType.Audio)
+        }
+
+    @Test
+    fun projection_singleTaskState_otherDevicesPackage_isCastToOtherAndScreen() =
         testScope.runTest {
             val latest by collectLastValue(underTest.projection)
 
@@ -73,31 +91,49 @@
                 MediaProjectionState.Projecting.SingleTask(
                     CAST_TO_OTHER_DEVICES_PACKAGE,
                     hostDeviceName = null,
-                    createTask(taskId = 1)
+                    createTask(taskId = 1),
                 )
 
             assertThat(latest).isInstanceOf(ProjectionChipModel.Projecting::class.java)
-            assertThat((latest as ProjectionChipModel.Projecting).type)
-                .isEqualTo(ProjectionChipModel.Type.CAST_TO_OTHER_DEVICE)
+            assertThat((latest as ProjectionChipModel.Projecting).receiver)
+                .isEqualTo(ProjectionChipModel.Receiver.CastToOtherDevice)
+            assertThat((latest as ProjectionChipModel.Projecting).contentType)
+                .isEqualTo(ProjectionChipModel.ContentType.Screen)
         }
 
     @Test
-    fun projection_entireScreenState_otherDevicesPackage_isCastToOtherDeviceChipType() =
+    fun projection_entireScreenState_otherDevicesPackage_isCastToOtherAndScreen() =
         testScope.runTest {
             val latest by collectLastValue(underTest.projection)
 
             mediaProjectionRepo.mediaProjectionState.value =
-                MediaProjectionState.Projecting.EntireScreen(
-                    CAST_TO_OTHER_DEVICES_PACKAGE,
-                )
+                MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
 
             assertThat(latest).isInstanceOf(ProjectionChipModel.Projecting::class.java)
-            assertThat((latest as ProjectionChipModel.Projecting).type)
-                .isEqualTo(ProjectionChipModel.Type.CAST_TO_OTHER_DEVICE)
+            assertThat((latest as ProjectionChipModel.Projecting).receiver)
+                .isEqualTo(ProjectionChipModel.Receiver.CastToOtherDevice)
+            assertThat((latest as ProjectionChipModel.Projecting).contentType)
+                .isEqualTo(ProjectionChipModel.ContentType.Screen)
         }
 
     @Test
-    fun projection_singleTaskState_normalPackage_isShareToAppChipType() =
+    @EnableFlags(FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP)
+    fun projection_noScreenState_normalPackage_isShareToAppAndAudio() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.projection)
+
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.NoScreen(NORMAL_PACKAGE)
+
+            assertThat(latest).isInstanceOf(ProjectionChipModel.Projecting::class.java)
+            assertThat((latest as ProjectionChipModel.Projecting).receiver)
+                .isEqualTo(ProjectionChipModel.Receiver.ShareToApp)
+            assertThat((latest as ProjectionChipModel.Projecting).contentType)
+                .isEqualTo(ProjectionChipModel.ContentType.Audio)
+        }
+
+    @Test
+    fun projection_singleTaskState_normalPackage_isShareToAppAndScreen() =
         testScope.runTest {
             val latest by collectLastValue(underTest.projection)
 
@@ -109,12 +145,14 @@
                 )
 
             assertThat(latest).isInstanceOf(ProjectionChipModel.Projecting::class.java)
-            assertThat((latest as ProjectionChipModel.Projecting).type)
-                .isEqualTo(ProjectionChipModel.Type.SHARE_TO_APP)
+            assertThat((latest as ProjectionChipModel.Projecting).receiver)
+                .isEqualTo(ProjectionChipModel.Receiver.ShareToApp)
+            assertThat((latest as ProjectionChipModel.Projecting).contentType)
+                .isEqualTo(ProjectionChipModel.ContentType.Screen)
         }
 
     @Test
-    fun projection_entireScreenState_normalPackage_isShareToAppChipType() =
+    fun projection_entireScreenState_normalPackage_isShareToAppAndScreen() =
         testScope.runTest {
             val latest by collectLastValue(underTest.projection)
 
@@ -122,8 +160,10 @@
                 MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
 
             assertThat(latest).isInstanceOf(ProjectionChipModel.Projecting::class.java)
-            assertThat((latest as ProjectionChipModel.Projecting).type)
-                .isEqualTo(ProjectionChipModel.Type.SHARE_TO_APP)
+            assertThat((latest as ProjectionChipModel.Projecting).receiver)
+                .isEqualTo(ProjectionChipModel.Receiver.ShareToApp)
+            assertThat((latest as ProjectionChipModel.Projecting).contentType)
+                .isEqualTo(ProjectionChipModel.ContentType.Screen)
         }
 
     companion object {
@@ -140,14 +180,14 @@
                 whenever(
                         this.checkPermission(
                             Manifest.permission.REMOTE_DISPLAY_PROVIDER,
-                            CAST_TO_OTHER_DEVICES_PACKAGE
+                            CAST_TO_OTHER_DEVICES_PACKAGE,
                         )
                     )
                     .thenReturn(PackageManager.PERMISSION_GRANTED)
                 whenever(
                         this.checkPermission(
                             Manifest.permission.REMOTE_DISPLAY_PROVIDER,
-                            NORMAL_PACKAGE
+                            NORMAL_PACKAGE,
                         )
                     )
                     .thenReturn(PackageManager.PERMISSION_DENIED)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndGenericShareToAppDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndGenericShareToAppDialogDelegateTest.kt
new file mode 100644
index 0000000..411d306
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndGenericShareToAppDialogDelegateTest.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.sharetoapp.ui.view
+
+import android.content.DialogInterface
+import android.content.applicationContext
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
+import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.mediaProjectionChipInteractor
+import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class EndGenericShareToAppDialogDelegateTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val sysuiDialog = mock<SystemUIDialog>()
+    private val underTest =
+        EndGenericShareToAppDialogDelegate(
+            kosmos.endMediaProjectionDialogHelper,
+            kosmos.applicationContext,
+            stopAction = kosmos.mediaProjectionChipInteractor::stopProjecting,
+        )
+
+    @Test
+    fun positiveButton_clickStopsRecording() =
+        kosmos.testScope.runTest {
+            underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+            assertThat(kosmos.fakeMediaProjectionRepository.stopProjectingInvoked).isFalse()
+
+            val clickListener = argumentCaptor<DialogInterface.OnClickListener>()
+            verify(sysuiDialog).setPositiveButton(any(), clickListener.capture())
+            clickListener.firstValue.onClick(mock<DialogInterface>(), 0)
+            runCurrent()
+
+            assertThat(kosmos.fakeMediaProjectionRepository.stopProjectingInvoked).isTrue()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareScreenToAppDialogDelegateTest.kt
similarity index 93%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareScreenToAppDialogDelegateTest.kt
index 325a42b..6885a6b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareScreenToAppDialogDelegateTest.kt
@@ -50,10 +50,10 @@
 
 @SmallTest
 @OptIn(ExperimentalCoroutinesApi::class)
-class EndShareToAppDialogDelegateTest : SysuiTestCase() {
+class EndShareScreenToAppDialogDelegateTest : SysuiTestCase() {
     private val kosmos = Kosmos().also { it.testCase = this }
     private val sysuiDialog = mock<SystemUIDialog>()
-    private lateinit var underTest: EndShareToAppDialogDelegate
+    private lateinit var underTest: EndShareScreenToAppDialogDelegate
 
     @Test
     fun icon() {
@@ -117,7 +117,7 @@
             MediaProjectionState.Projecting.SingleTask(
                 HOST_PACKAGE,
                 hostDeviceName = null,
-                createTask(taskId = 1, baseIntent = baseIntent)
+                createTask(taskId = 1, baseIntent = baseIntent),
             )
         )
 
@@ -142,7 +142,7 @@
             MediaProjectionState.Projecting.SingleTask(
                 HOST_PACKAGE,
                 hostDeviceName = null,
-                createTask(taskId = 1, baseIntent = baseIntent)
+                createTask(taskId = 1, baseIntent = baseIntent),
             )
         )
 
@@ -181,7 +181,7 @@
             verify(sysuiDialog)
                 .setPositiveButton(
                     eq(R.string.share_to_app_stop_dialog_button),
-                    clickListener.capture()
+                    clickListener.capture(),
                 )
 
             // Verify that clicking the button stops the recording
@@ -195,12 +195,13 @@
 
     private fun createAndSetDelegate(state: MediaProjectionState.Projecting) {
         underTest =
-            EndShareToAppDialogDelegate(
+            EndShareScreenToAppDialogDelegate(
                 kosmos.endMediaProjectionDialogHelper,
                 kosmos.applicationContext,
                 stopAction = kosmos.mediaProjectionChipInteractor::stopProjecting,
                 ProjectionChipModel.Projecting(
-                    ProjectionChipModel.Type.SHARE_TO_APP,
+                    ProjectionChipModel.Receiver.ShareToApp,
+                    ProjectionChipModel.ContentType.Screen,
                     state,
                 ),
             )
@@ -213,7 +214,7 @@
             MediaProjectionState.Projecting.SingleTask(
                 HOST_PACKAGE,
                 hostDeviceName = null,
-                createTask(taskId = 1)
+                createTask(taskId = 1),
             )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
index 791a21d..d7d57c8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
@@ -17,12 +17,15 @@
 package com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel
 
 import android.content.DialogInterface
+import android.platform.test.annotations.EnableFlags
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.internal.jank.Cuj
+import com.android.systemui.Flags.FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP
 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
 import com.android.systemui.kosmos.Kosmos
@@ -35,7 +38,8 @@
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.CAST_TO_OTHER_DEVICES_PACKAGE
 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.sharetoapp.ui.view.EndShareToAppDialogDelegate
+import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndGenericShareToAppDialogDelegate
+import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndShareScreenToAppDialogDelegate
 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
@@ -62,7 +66,8 @@
     private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository
     private val systemClock = kosmos.fakeSystemClock
 
-    private val mockShareDialog = mock<SystemUIDialog>()
+    private val mockScreenShareDialog = mock<SystemUIDialog>()
+    private val mockGenericShareDialog = mock<SystemUIDialog>()
     private val chipBackgroundView = mock<ChipBackgroundContainer>()
     private val chipView =
         mock<View>().apply {
@@ -80,8 +85,10 @@
     fun setUp() {
         setUpPackageManagerForMediaProjection(kosmos)
 
-        whenever(kosmos.mockSystemUIDialogFactory.create(any<EndShareToAppDialogDelegate>()))
-            .thenReturn(mockShareDialog)
+        whenever(kosmos.mockSystemUIDialogFactory.create(any<EndShareScreenToAppDialogDelegate>()))
+            .thenReturn(mockScreenShareDialog)
+        whenever(kosmos.mockSystemUIDialogFactory.create(any<EndGenericShareToAppDialogDelegate>()))
+            .thenReturn(mockGenericShareDialog)
     }
 
     @Test
@@ -95,6 +102,21 @@
         }
 
     @Test
+    @EnableFlags(FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP)
+    fun chip_noScreenState_otherDevicesPackage_isHidden() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.NoScreen(
+                    CAST_TO_OTHER_DEVICES_PACKAGE,
+                    hostDeviceName = null,
+                )
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+        }
+
+    @Test
     fun chip_singleTaskState_otherDevicesPackage_isHidden() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
@@ -121,6 +143,26 @@
         }
 
     @Test
+    @EnableFlags(FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP)
+    fun chip_noScreenState_normalPackage_isShownAsIconOnly() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.NoScreen(NORMAL_PACKAGE, hostDeviceName = null)
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
+            val icon =
+                (((latest as OngoingActivityChipModel.Shown).icon)
+                        as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
+                    .impl as Icon.Resource
+            assertThat(icon.res).isEqualTo(R.drawable.ic_present_to_all)
+            // This content description is just generic "Sharing content", not "Sharing screen"
+            assertThat((icon.contentDescription as ContentDescription.Resource).res)
+                .isEqualTo(R.string.share_to_app_chip_accessibility_label_generic)
+        }
+
+    @Test
     fun chip_singleTaskState_normalPackage_isShownAsTimer() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
@@ -170,7 +212,7 @@
 
             // WHEN the stop action on the dialog is clicked
             val dialogStopAction =
-                getStopActionFromDialog(latest, chipView, mockShareDialog, kosmos)
+                getStopActionFromDialog(latest, chipView, mockScreenShareDialog, kosmos)
             dialogStopAction.onClick(mock<DialogInterface>(), 0)
 
             // THEN the chip is immediately hidden...
@@ -222,7 +264,28 @@
         }
 
     @Test
-    fun chip_entireScreen_clickListenerShowsShareDialog() =
+    @EnableFlags(FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP)
+    fun chip_noScreen_clickListenerShowsGenericShareDialog() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.NoScreen(NORMAL_PACKAGE)
+
+            val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+            assertThat(clickListener).isNotNull()
+
+            clickListener!!.onClick(chipView)
+            verify(kosmos.mockDialogTransitionAnimator)
+                .showFromView(
+                    eq(mockGenericShareDialog),
+                    eq(chipBackgroundView),
+                    any(),
+                    anyBoolean(),
+                )
+        }
+
+    @Test
+    fun chip_entireScreen_clickListenerShowsScreenShareDialog() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
             mediaProjectionRepo.mediaProjectionState.value =
@@ -234,7 +297,7 @@
             clickListener!!.onClick(chipView)
             verify(kosmos.mockDialogTransitionAnimator)
                 .showFromView(
-                    eq(mockShareDialog),
+                    eq(mockScreenShareDialog),
                     eq(chipBackgroundView),
                     any(),
                     anyBoolean(),
@@ -242,7 +305,7 @@
         }
 
     @Test
-    fun chip_singleTask_clickListenerShowsShareDialog() =
+    fun chip_singleTask_clickListenerShowsScreenShareDialog() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chip)
             mediaProjectionRepo.mediaProjectionState.value =
@@ -258,7 +321,7 @@
             clickListener!!.onClick(chipView)
             verify(kosmos.mockDialogTransitionAnimator)
                 .showFromView(
-                    eq(mockShareDialog),
+                    eq(mockScreenShareDialog),
                     eq(chipBackgroundView),
                     any(),
                     anyBoolean(),
@@ -281,12 +344,7 @@
 
             val cujCaptor = argumentCaptor<DialogCuj>()
             verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    any(),
-                    any(),
-                    cujCaptor.capture(),
-                    anyBoolean(),
-                )
+                .showFromView(any(), any(), cujCaptor.capture(), anyBoolean())
 
             assertThat(cujCaptor.firstValue.cujType)
                 .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP)
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2c5fb56..cf07abb 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -342,8 +342,12 @@
 
     <!-- Content description for the status bar chip shown to the user when they're sharing their screen to another app on the device [CHAR LIMIT=NONE] -->
     <string name="share_to_app_chip_accessibility_label">Sharing screen</string>
+    <!-- Content description for the status bar chip shown to the user when they're sharing their screen or audio to another app on the device [CHAR LIMIT=NONE] -->
+    <string name="share_to_app_chip_accessibility_label_generic">Sharing content</string>
     <!-- Title for a dialog shown to the user that will let them stop sharing their screen to another app on the device [CHAR LIMIT=50] -->
     <string name="share_to_app_stop_dialog_title">Stop sharing screen?</string>
+    <!-- Title for a dialog shown to the user that will let them stop sharing their screen or audio to another app on the device [CHAR LIMIT=50] -->
+    <string name="share_to_app_stop_dialog_title_generic">Stop sharing?</string>
     <!-- Text telling a user that they're currently sharing their entire screen to [host_app_name] (i.e. [host_app_name] can currently see all screen content) [CHAR LIMIT=150] -->
     <string name="share_to_app_stop_dialog_message_entire_screen_with_host_app">You\'re currently sharing your entire screen with <xliff:g id="host_app_name" example="Screen Recorder App">%1$s</xliff:g></string>
     <!-- Text telling a user that they're currently sharing their entire screen to an app (but we don't know what app) [CHAR LIMIT=150] -->
@@ -352,6 +356,8 @@
     <string name="share_to_app_stop_dialog_message_single_app_specific">You\'re currently sharing <xliff:g id="app_being_shared_name" example="Photos App">%1$s</xliff:g></string>
     <!-- Text telling a user that they're currently sharing their screen [CHAR LIMIT=150] -->
     <string name="share_to_app_stop_dialog_message_single_app_generic">You\'re currently sharing an app</string>
+    <!-- Text telling a user that they're currently sharing something to an app [CHAR LIMIT=100] -->
+    <string name="share_to_app_stop_dialog_message_generic">You\'re currently sharing with an app</string>
     <!-- Button to stop screen sharing [CHAR LIMIT=35] -->
     <string name="share_to_app_stop_dialog_button">Stop sharing</string>
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/model/MediaProjectionState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/model/MediaProjectionState.kt
index 82b4825..2fa3405 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/model/MediaProjectionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/model/MediaProjectionState.kt
@@ -32,10 +32,8 @@
      *   media projection. Null if the media projection is going to this same device (e.g. another
      *   app is recording the screen).
      */
-    sealed class Projecting(
-        open val hostPackage: String,
-        open val hostDeviceName: String?,
-    ) : MediaProjectionState {
+    sealed class Projecting(open val hostPackage: String, open val hostDeviceName: String?) :
+        MediaProjectionState {
         /** The entire screen is being projected. */
         data class EntireScreen(
             override val hostPackage: String,
@@ -48,5 +46,11 @@
             override val hostDeviceName: String?,
             val task: RunningTaskInfo,
         ) : Projecting(hostPackage, hostDeviceName)
+
+        /** The screen is not being projected, only audio is being projected. */
+        data class NoScreen(
+            override val hostPackage: String,
+            override val hostDeviceName: String? = null,
+        ) : Projecting(hostPackage, hostDeviceName)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
index 5704e80..35efd75 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
@@ -23,6 +23,7 @@
 import android.os.Handler
 import android.view.ContentRecordingSession
 import android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY
+import com.android.systemui.Flags
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -94,7 +95,7 @@
                                 {},
                                 { "MediaProjectionManager.Callback#onStart" },
                             )
-                            trySendWithFailureLogging(CallbackEvent.OnStart, TAG)
+                            trySendWithFailureLogging(CallbackEvent.OnStart(info), TAG)
                         }
 
                         override fun onStop(info: MediaProjectionInfo?) {
@@ -109,7 +110,7 @@
 
                         override fun onRecordingSessionSet(
                             info: MediaProjectionInfo,
-                            session: ContentRecordingSession?
+                            session: ContentRecordingSession?,
                         ) {
                             logger.log(
                                 TAG,
@@ -142,7 +143,21 @@
             // #onRecordingSessionSet and we don't emit "Projecting".
             .mapLatest {
                 when (it) {
-                    is CallbackEvent.OnStart,
+                    is CallbackEvent.OnStart -> {
+                        if (!Flags.statusBarShowAudioOnlyProjectionChip()) {
+                            return@mapLatest MediaProjectionState.NotProjecting
+                        }
+                        // It's possible for a projection to be audio-only, in which case `OnStart`
+                        // will occur but `OnRecordingSessionSet` will not. We should still consider
+                        // us to be projecting even if only audio is projecting. See b/373308507.
+                        if (it.info != null) {
+                            MediaProjectionState.Projecting.NoScreen(
+                                hostPackage = it.info.packageName
+                            )
+                        } else {
+                            MediaProjectionState.NotProjecting
+                        }
+                    }
                     is CallbackEvent.OnStop -> MediaProjectionState.NotProjecting
                     is CallbackEvent.OnRecordingSessionSet -> stateForSession(it.info, it.session)
                 }
@@ -155,7 +170,7 @@
 
     private suspend fun stateForSession(
         info: MediaProjectionInfo,
-        session: ContentRecordingSession?
+        session: ContentRecordingSession?,
     ): MediaProjectionState {
         if (session == null) {
             return MediaProjectionState.NotProjecting
@@ -184,7 +199,7 @@
      * the correct callback ordering.
      */
     sealed interface CallbackEvent {
-        data object OnStart : CallbackEvent
+        data class OnStart(val info: MediaProjectionInfo?) : CallbackEvent
 
         data object OnStop : CallbackEvent
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt
index 118639c..ccc54f1 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt
@@ -68,6 +68,7 @@
                     }
                 }
                 is MediaProjectionState.Projecting.EntireScreen,
+                is MediaProjectionState.Projecting.NoScreen,
                 is MediaProjectionState.NotProjecting -> {
                     flowOf(TaskSwitchState.NotProjectingTask)
                 }
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 d4ad6ee..1107206 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
@@ -68,23 +68,24 @@
     private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
     @StatusBarChipsLog private val logger: LogBuffer,
 ) : OngoingActivityChipViewModel {
-    /**
-     * The cast chip to show, based only on MediaProjection API events.
-     *
-     * This chip will only be [OngoingActivityChipModel.Shown] when the user is casting their
-     * *screen*. If the user is only casting audio, this chip will be
-     * [OngoingActivityChipModel.Hidden].
-     */
+    /** The cast chip to show, based only on MediaProjection API events. */
     private val projectionChip: StateFlow<OngoingActivityChipModel> =
         mediaProjectionChipInteractor.projection
             .map { projectionModel ->
                 when (projectionModel) {
                     is ProjectionChipModel.NotProjecting -> OngoingActivityChipModel.Hidden()
                     is ProjectionChipModel.Projecting -> {
-                        if (projectionModel.type != ProjectionChipModel.Type.CAST_TO_OTHER_DEVICE) {
-                            OngoingActivityChipModel.Hidden()
-                        } else {
-                            createCastScreenToOtherDeviceChip(projectionModel)
+                        when (projectionModel.receiver) {
+                            ProjectionChipModel.Receiver.CastToOtherDevice -> {
+                                when (projectionModel.contentType) {
+                                    ProjectionChipModel.ContentType.Screen ->
+                                        createCastScreenToOtherDeviceChip(projectionModel)
+                                    ProjectionChipModel.ContentType.Audio ->
+                                        createIconOnlyCastChip(deviceName = null)
+                                }
+                            }
+                            ProjectionChipModel.Receiver.ShareToApp ->
+                                OngoingActivityChipModel.Hidden()
                         }
                     }
                 }
@@ -98,9 +99,9 @@
      * This chip will be [OngoingActivityChipModel.Shown] when the user is casting their screen *or*
      * their audio.
      *
-     * The MediaProjection APIs are not invoked for casting *only audio* to another device because
-     * MediaProjection is only concerned with *screen* sharing (see b/342169876). We listen to
-     * MediaRouter APIs here to cover audio-only casting.
+     * The MediaProjection APIs are typically not invoked for casting *only audio* to another device
+     * because MediaProjection is only concerned with *screen* sharing (see b/342169876). We listen
+     * to MediaRouter APIs here to cover audio-only casting.
      *
      * Note that this means we will start showing the cast chip before the casting actually starts,
      * for **both** audio-only casting and screen casting. MediaRouter is aware of all
@@ -139,7 +140,7 @@
                         str1 = projection.logName
                         str2 = router.logName
                     },
-                    { "projectionChip=$str1 > routerChip=$str2" }
+                    { "projectionChip=$str1 > routerChip=$str2" },
                 )
 
                 // A consequence of b/269975671 is that MediaRouter and MediaProjection APIs fire at
@@ -186,7 +187,7 @@
     }
 
     private fun createCastScreenToOtherDeviceChip(
-        state: ProjectionChipModel.Projecting,
+        state: ProjectionChipModel.Projecting
     ): OngoingActivityChipModel.Shown {
         return OngoingActivityChipModel.Shown.Timer(
             icon =
@@ -195,7 +196,7 @@
                         CAST_TO_OTHER_DEVICE_ICON,
                         // This string is "Casting screen"
                         ContentDescription.Resource(
-                            R.string.cast_screen_to_other_device_chip_accessibility_label,
+                            R.string.cast_screen_to_other_device_chip_accessibility_label
                         ),
                     )
                 ),
@@ -236,9 +237,7 @@
         )
     }
 
-    private fun createCastScreenToOtherDeviceDialogDelegate(
-        state: ProjectionChipModel.Projecting,
-    ) =
+    private fun createCastScreenToOtherDeviceDialogDelegate(state: ProjectionChipModel.Projecting) =
         EndCastScreenToOtherDeviceDialogDelegate(
             endMediaProjectionDialogHelper,
             context,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
index c5f78d2..4b0c3cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.chips.mediaprojection.domain.interactor
 
 import android.content.pm.PackageManager
+import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.LogBuffer
@@ -59,23 +60,43 @@
                         ProjectionChipModel.NotProjecting
                     }
                     is MediaProjectionState.Projecting -> {
-                        val type =
+                        val receiver =
                             if (packageHasCastingCapabilities(packageManager, state.hostPackage)) {
-                                ProjectionChipModel.Type.CAST_TO_OTHER_DEVICE
+                                ProjectionChipModel.Receiver.CastToOtherDevice
                             } else {
-                                ProjectionChipModel.Type.SHARE_TO_APP
+                                ProjectionChipModel.Receiver.ShareToApp
                             }
+                        val contentType =
+                            if (Flags.statusBarShowAudioOnlyProjectionChip()) {
+                                when (state) {
+                                    is MediaProjectionState.Projecting.EntireScreen,
+                                    is MediaProjectionState.Projecting.SingleTask ->
+                                        ProjectionChipModel.ContentType.Screen
+                                    is MediaProjectionState.Projecting.NoScreen ->
+                                        ProjectionChipModel.ContentType.Audio
+                                }
+                            } else {
+                                ProjectionChipModel.ContentType.Screen
+                            }
+
                         logger.log(
                             TAG,
                             LogLevel.INFO,
                             {
-                                str1 = type.name
-                                str2 = state.hostPackage
-                                str3 = state.hostDeviceName
+                                bool1 = receiver == ProjectionChipModel.Receiver.CastToOtherDevice
+                                bool2 = contentType == ProjectionChipModel.ContentType.Screen
+                                str1 = state.hostPackage
+                                str2 = state.hostDeviceName
                             },
-                            { "State: Projecting(type=$str1 hostPackage=$str2 hostDevice=$str3)" }
+                            {
+                                "State: Projecting(" +
+                                    "receiver=${if (bool1) "CastToOtherDevice" else "ShareToApp"} " +
+                                    "contentType=${if (bool2) "Screen" else "Audio"} " +
+                                    "hostPackage=$str1 " +
+                                    "hostDevice=$str2)"
+                            },
                         )
-                        ProjectionChipModel.Projecting(type, state)
+                        ProjectionChipModel.Projecting(receiver, contentType, state)
                     }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/ProjectionChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/ProjectionChipModel.kt
index 85682f5..c6283e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/ProjectionChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/ProjectionChipModel.kt
@@ -28,16 +28,22 @@
 
     /** Media is currently being projected. */
     data class Projecting(
-        val type: Type,
+        val receiver: Receiver,
+        val contentType: ContentType,
         val projectionState: MediaProjectionState.Projecting,
     ) : ProjectionChipModel()
 
-    enum class Type {
-        /**
-         * This projection is sharing your phone screen content to another app on the same device.
-         */
-        SHARE_TO_APP,
-        /** This projection is sharing your phone screen content to a different device. */
-        CAST_TO_OTHER_DEVICE,
+    enum class Receiver {
+        /** This projection is sharing to another app on the same device. */
+        ShareToApp,
+        /** This projection is sharing to a different device. */
+        CastToOtherDevice,
+    }
+
+    enum class ContentType {
+        /** This projection is sharing your device's screen content. */
+        Screen,
+        /** This projection is sharing your device's audio (but *not* screen). */
+        Audio,
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndGenericShareToAppDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndGenericShareToAppDialogDelegate.kt
new file mode 100644
index 0000000..8ec0567
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndGenericShareToAppDialogDelegate.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.sharetoapp.ui.view
+
+import android.content.Context
+import android.os.Bundle
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
+import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel.Companion.SHARE_TO_APP_ICON
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+/**
+ * A dialog that lets the user stop an ongoing share-to-app event. The user could be sharing their
+ * screen or just sharing their audio. This dialog uses generic strings to handle both cases well.
+ */
+class EndGenericShareToAppDialogDelegate(
+    private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+    private val context: Context,
+    private val stopAction: () -> Unit,
+) : SystemUIDialog.Delegate {
+    override fun createDialog(): SystemUIDialog {
+        return endMediaProjectionDialogHelper.createDialog(this)
+    }
+
+    override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+        val message = context.getString(R.string.share_to_app_stop_dialog_message_generic)
+        with(dialog) {
+            setIcon(SHARE_TO_APP_ICON)
+            setTitle(R.string.share_to_app_stop_dialog_title_generic)
+            setMessage(message)
+            // 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,
+                endMediaProjectionDialogHelper.wrapStopAction(stopAction),
+            )
+        }
+    }
+}
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/EndShareScreenToAppDialogDelegate.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegate.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareScreenToAppDialogDelegate.kt
index d10bd77..053016e3 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/EndShareScreenToAppDialogDelegate.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.statusbar.phone.SystemUIDialog
 
 /** A dialog that lets the user stop an ongoing share-screen-to-app event. */
-class EndShareToAppDialogDelegate(
+class EndShareScreenToAppDialogDelegate(
     private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
     private val context: Context,
     private val stopAction: () -> Unit,
@@ -71,7 +71,7 @@
             if (hostAppName != null) {
                 context.getString(
                     R.string.share_to_app_stop_dialog_message_entire_screen_with_host_app,
-                    hostAppName
+                    hostAppName,
                 )
             } else {
                 context.getString(R.string.share_to_app_stop_dialog_message_entire_screen)
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 d99a916..11d077f 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
@@ -32,7 +32,8 @@
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractor
 import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
 import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
-import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndShareToAppDialogDelegate
+import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndGenericShareToAppDialogDelegate
+import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndShareScreenToAppDialogDelegate
 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
@@ -68,10 +69,17 @@
                 when (projectionModel) {
                     is ProjectionChipModel.NotProjecting -> OngoingActivityChipModel.Hidden()
                     is ProjectionChipModel.Projecting -> {
-                        if (projectionModel.type != ProjectionChipModel.Type.SHARE_TO_APP) {
-                            OngoingActivityChipModel.Hidden()
-                        } else {
-                            createShareToAppChip(projectionModel)
+                        when (projectionModel.receiver) {
+                            ProjectionChipModel.Receiver.ShareToApp -> {
+                                when (projectionModel.contentType) {
+                                    ProjectionChipModel.ContentType.Screen ->
+                                        createShareScreenToAppChip(projectionModel)
+                                    ProjectionChipModel.ContentType.Audio ->
+                                        createIconOnlyShareToAppChip()
+                                }
+                            }
+                            ProjectionChipModel.Receiver.CastToOtherDevice ->
+                                OngoingActivityChipModel.Hidden()
                         }
                     }
                 }
@@ -105,8 +113,8 @@
         mediaProjectionChipInteractor.stopProjecting()
     }
 
-    private fun createShareToAppChip(
-        state: ProjectionChipModel.Projecting,
+    private fun createShareScreenToAppChip(
+        state: ProjectionChipModel.Projecting
     ): OngoingActivityChipModel.Shown {
         return OngoingActivityChipModel.Shown.Timer(
             icon =
@@ -120,11 +128,33 @@
             // TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
             startTimeMs = systemClock.elapsedRealtime(),
             createDialogLaunchOnClickListener(
-                createShareToAppDialogDelegate(state),
+                createShareScreenToAppDialogDelegate(state),
+                dialogTransitionAnimator,
+                DialogCuj(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, tag = "Share to app"),
+                logger,
+                TAG,
+            ),
+        )
+    }
+
+    private fun createIconOnlyShareToAppChip(): OngoingActivityChipModel.Shown {
+        return OngoingActivityChipModel.Shown.IconOnly(
+            icon =
+                OngoingActivityChipModel.ChipIcon.SingleColorIcon(
+                    Icon.Resource(
+                        SHARE_TO_APP_ICON,
+                        ContentDescription.Resource(
+                            R.string.share_to_app_chip_accessibility_label_generic
+                        ),
+                    )
+                ),
+            colors = ColorsModel.Red,
+            createDialogLaunchOnClickListener(
+                createGenericShareToAppDialogDelegate(),
                 dialogTransitionAnimator,
                 DialogCuj(
                     Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP,
-                    tag = "Share to app",
+                    tag = "Share to app audio only",
                 ),
                 logger,
                 TAG,
@@ -132,14 +162,21 @@
         )
     }
 
-    private fun createShareToAppDialogDelegate(state: ProjectionChipModel.Projecting) =
-        EndShareToAppDialogDelegate(
+    private fun createShareScreenToAppDialogDelegate(state: ProjectionChipModel.Projecting) =
+        EndShareScreenToAppDialogDelegate(
             endMediaProjectionDialogHelper,
             context,
             stopAction = this::stopProjectingFromDialog,
             state,
         )
 
+    private fun createGenericShareToAppDialogDelegate() =
+        EndGenericShareToAppDialogDelegate(
+            endMediaProjectionDialogHelper,
+            context,
+            stopAction = this::stopProjectingFromDialog,
+        )
+
     companion object {
         @DrawableRes val SHARE_TO_APP_ICON = R.drawable.ic_present_to_all
         private const val TAG = "ShareToAppVM"