Merge "Add audio sharing button and highlight audio sharing device." into main
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index 43ff7b6..76d10cc 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -256,6 +256,24 @@
                 app:constraint_referenced_ids="pair_new_device_button,bluetooth_auto_on_toggle_info_text" />
 
             <Button
+                android:id="@+id/audio_sharing_button"
+                style="@style/Widget.Dialog.Button.BorderButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="9dp"
+                android:layout_marginBottom="@dimen/dialog_bottom_padding"
+                android:layout_marginEnd="@dimen/dialog_side_padding"
+                android:layout_marginStart="@dimen/dialog_side_padding"
+                android:ellipsize="end"
+                android:maxLines="1"
+                android:text="@string/quick_settings_bluetooth_audio_sharing_button"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/barrier"
+                app:layout_constraintVertical_bias="1"
+                android:visibility="gone" />
+
+            <Button
                 android:id="@+id/done_button"
                 style="@style/Widget.Dialog.Button"
                 android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2fed457..f60f6c7 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -678,6 +678,8 @@
     <string name="turn_on_bluetooth">Use Bluetooth</string>
     <!-- QuickSettings: Bluetooth dialog device connected default summary [CHAR LIMIT=NONE]-->
     <string name="quick_settings_bluetooth_device_connected">Connected</string>
+    <!-- QuickSettings: Bluetooth dialog device in audio sharing default summary [CHAR LIMIT=50]-->
+    <string name="quick_settings_bluetooth_device_audio_sharing">Audio Sharing</string>
     <!-- QuickSettings: Bluetooth dialog device saved default summary [CHAR LIMIT=NONE]-->
     <string name="quick_settings_bluetooth_device_saved">Saved</string>
     <!-- QuickSettings: Accessibility label to disconnect a device [CHAR LIMIT=NONE]-->
@@ -690,6 +692,10 @@
     <string name="turn_on_bluetooth_auto_info_disabled">Features like Quick Share and Find My Device use Bluetooth</string>
     <!-- QuickSettings: Bluetooth auto on info text when enabled [CHAR LIMIT=NONE]-->
     <string name="turn_on_bluetooth_auto_info_enabled">Bluetooth will turn on tomorrow morning</string>
+    <!-- QuickSettings: Bluetooth dialog audio sharing button text [CHAR LIMIT=50]-->
+    <string name="quick_settings_bluetooth_audio_sharing_button">Audio Sharing</string>
+    <!-- QuickSettings: Bluetooth dialog audio sharing button text when sharing audio [CHAR LIMIT=50]-->
+    <string name="quick_settings_bluetooth_audio_sharing_button_sharing">Sharing Audio</string>
 
     <!-- QuickSettings: Bluetooth secondary label for the battery level of a connected device [CHAR LIMIT=20]-->
     <string name="quick_settings_bluetooth_secondary_label_battery_level"><xliff:g id="battery_level_as_percentage">%s</xliff:g> battery</string>
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
new file mode 100644
index 0000000..e44f054
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 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.bluetooth.qsdialog
+
+import androidx.annotation.StringRes
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
+
+internal sealed class AudioSharingButtonState {
+    object Gone : AudioSharingButtonState()
+    data class Visible(@StringRes val resId: Int) : AudioSharingButtonState()
+}
+
+/** Holds business logic for the audio sharing state. */
+@SysUISingleton
+internal class AudioSharingInteractor
+@Inject
+constructor(
+    private val localBluetoothManager: LocalBluetoothManager?,
+    bluetoothStateInteractor: BluetoothStateInteractor,
+    deviceItemInteractor: DeviceItemInteractor,
+    @Application private val coroutineScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
+    /** Flow representing the update of AudioSharingButtonState. */
+    internal val audioSharingButtonStateUpdate: Flow<AudioSharingButtonState> =
+        combine(
+                bluetoothStateInteractor.bluetoothStateUpdate,
+                deviceItemInteractor.deviceItemUpdate
+            ) { bluetoothState, deviceItem ->
+                getButtonState(bluetoothState, deviceItem)
+            }
+            .flowOn(backgroundDispatcher)
+            .stateIn(
+                coroutineScope,
+                SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
+                initialValue = AudioSharingButtonState.Gone
+            )
+
+    private fun getButtonState(
+        bluetoothState: Boolean,
+        deviceItem: List<DeviceItem>
+    ): AudioSharingButtonState {
+        return when {
+            // Don't show button when bluetooth is off
+            !bluetoothState -> AudioSharingButtonState.Gone
+            // Show sharing audio when broadcasting
+            BluetoothUtils.isBroadcasting(localBluetoothManager) ->
+                AudioSharingButtonState.Visible(
+                    R.string.quick_settings_bluetooth_audio_sharing_button_sharing
+                )
+            // When not broadcasting, don't show button if there's connected source in any device
+            deviceItem.any {
+                BluetoothUtils.hasConnectedBroadcastSource(
+                    it.cachedBluetoothDevice,
+                    localBluetoothManager
+                )
+            } -> AudioSharingButtonState.Gone
+            // Show audio sharing when there's a connected LE audio device
+            deviceItem.any { BluetoothUtils.isActiveLeAudioDevice(it.cachedBluetoothDevice) } ->
+                AudioSharingButtonState.Visible(
+                    R.string.quick_settings_bluetooth_audio_sharing_button
+                )
+            else -> AudioSharingButtonState.Gone
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
index 94d7af7..17f9e63 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
@@ -25,12 +25,17 @@
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
 
 /** Holds business logic for the Bluetooth Dialog's bluetooth and device connection state */
 @SysUISingleton
@@ -40,9 +45,10 @@
     private val localBluetoothManager: LocalBluetoothManager?,
     private val logger: BluetoothTileDialogLogger,
     @Application private val coroutineScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) {
 
-    internal val bluetoothStateUpdate: StateFlow<Boolean?> =
+    internal val bluetoothStateUpdate: StateFlow<Boolean> =
         conflatedCallbackFlow {
                 val listener =
                     object : BluetoothCallback {
@@ -64,16 +70,22 @@
                 localBluetoothManager?.eventManager?.registerCallback(listener)
                 awaitClose { localBluetoothManager?.eventManager?.unregisterCallback(listener) }
             }
+            .onStart { emit(isBluetoothEnabled()) }
+            .flowOn(backgroundDispatcher)
             .stateIn(
                 coroutineScope,
                 SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
-                initialValue = null
+                initialValue = false
             )
 
-    internal var isBluetoothEnabled: Boolean
-        get() = localBluetoothManager?.bluetoothAdapter?.isEnabled == true
-        set(value) {
-            if (isBluetoothEnabled != value) {
+    suspend fun isBluetoothEnabled(): Boolean =
+        withContext(backgroundDispatcher) {
+            localBluetoothManager?.bluetoothAdapter?.isEnabled == true
+        }
+
+    suspend fun setBluetoothEnabled(value: Boolean) {
+        withContext(backgroundDispatcher) {
+            if (isBluetoothEnabled() != value) {
                 localBluetoothManager?.bluetoothAdapter?.apply {
                     if (value) enable() else disable()
                     logger.logBluetoothState(
@@ -83,6 +95,7 @@
                 }
             }
         }
+    }
 
     companion object {
         private const val TAG = "BtStateInteractor"
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index c7d171d..dd8c0df 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -27,6 +27,7 @@
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.view.accessibility.AccessibilityNodeInfo
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
+import android.widget.Button
 import android.widget.ImageView
 import android.widget.ProgressBar
 import android.widget.Switch
@@ -59,7 +60,6 @@
 internal constructor(
     @Assisted private val initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
     @Assisted private val cachedContentHeight: Int,
-    @Assisted private val bluetoothToggleInitialValue: Boolean,
     @Assisted private val bluetoothTileDialogCallback: BluetoothTileDialogCallback,
     @Assisted private val dismissListener: Runnable,
     @Main private val mainDispatcher: CoroutineDispatcher,
@@ -69,8 +69,7 @@
     private val systemuiDialogFactory: SystemUIDialog.Factory,
 ) : SystemUIDialog.Delegate {
 
-    private val mutableBluetoothStateToggle: MutableStateFlow<Boolean> =
-        MutableStateFlow(bluetoothToggleInitialValue)
+    private val mutableBluetoothStateToggle: MutableStateFlow<Boolean?> = MutableStateFlow(null)
     internal val bluetoothStateToggle
         get() = mutableBluetoothStateToggle.asStateFlow()
 
@@ -99,7 +98,6 @@
         fun create(
             initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
             cachedContentHeight: Int,
-            bluetoothEnabled: Boolean,
             dialogCallback: BluetoothTileDialogCallback,
             dimissListener: Runnable
         ): BluetoothTileDialogDelegate
@@ -130,6 +128,9 @@
         getPairNewDeviceButton(dialog).setOnClickListener {
             bluetoothTileDialogCallback.onPairNewDeviceClicked(it)
         }
+        getAudioSharingButtonView(dialog).setOnClickListener {
+            bluetoothTileDialogCallback.onAudioSharingButtonClicked(it)
+        }
         getScrollViewContent(dialog).apply {
             minimumHeight =
                 resources.getDimensionPixelSize(initialUiProperties.scrollViewMinHeightResId)
@@ -211,9 +212,19 @@
         getAutoOnToggleInfoTextView(dialog).text = dialog.context.getString(infoResId)
     }
 
+    internal fun onAudioSharingButtonUpdated(
+        dialog: SystemUIDialog,
+        visibility: Int,
+        label: String?
+    ) {
+        getAudioSharingButtonView(dialog).apply {
+            this.visibility = visibility
+            label?.let { text = it }
+        }
+    }
+
     private fun setupToggle(dialog: SystemUIDialog) {
         val toggleView = getToggleView(dialog)
-        toggleView.isChecked = bluetoothToggleInitialValue
         toggleView.setOnCheckedChangeListener { view, isChecked ->
             mutableBluetoothStateToggle.value = isChecked
             view.apply {
@@ -259,6 +270,10 @@
         return dialog.requireViewById(R.id.bluetooth_auto_on_toggle)
     }
 
+    private fun getAudioSharingButtonView(dialog: SystemUIDialog): Button {
+        return dialog.requireViewById(R.id.audio_sharing_button)
+    }
+
     private fun getAutoOnToggleView(dialog: SystemUIDialog): View {
         return dialog.requireViewById(R.id.bluetooth_auto_on_toggle_layout)
     }
@@ -412,6 +427,8 @@
         const val ACTION_PREVIOUSLY_CONNECTED_DEVICE =
             "com.android.settings.PREVIOUSLY_CONNECTED_DEVICE"
         const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS"
+        const val ACTION_AUDIO_SHARING =
+            "com.google.android.settings.BLUETOOTH_AUDIO_SHARING_SETTINGS"
         const val DISABLED_ALPHA = 0.3f
         const val ENABLED_ALPHA = 1f
         const val PROGRESS_BAR_ANIMATION_DURATION_MS = 1500L
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
index add1647..b592b8e 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
@@ -30,9 +30,12 @@
     @UiEvent(doc = "Connected device clicked to active") CONNECTED_DEVICE_SET_ACTIVE(1499),
     @UiEvent(doc = "Saved clicked to connect") SAVED_DEVICE_CONNECT(1500),
     @UiEvent(doc = "Active device clicked to disconnect") ACTIVE_DEVICE_DISCONNECT(1507),
+    @UiEvent(doc = "Audio sharing device clicked, do nothing") AUDIO_SHARING_DEVICE_CLICKED(1699),
     @UiEvent(doc = "Connected other device clicked to disconnect")
     CONNECTED_OTHER_DEVICE_DISCONNECT(1508),
-    @UiEvent(doc = "The auto on toggle is clicked") BLUETOOTH_AUTO_ON_TOGGLE_CLICKED(1617);
+    @UiEvent(doc = "The auto on toggle is clicked") BLUETOOTH_AUTO_ON_TOGGLE_CLICKED(1617),
+    @UiEvent(doc = "The audio sharing button is clicked")
+    BLUETOOTH_AUDIO_SHARING_BUTTON_CLICKED(1700);
 
     override fun getId() = metricId
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
index e65b657..eb919e3 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -28,9 +28,11 @@
 import androidx.annotation.VisibleForTesting
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.UiEventLogger
+import com.android.settingslib.bluetooth.BluetoothUtils
 import com.android.systemui.Prefs
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_AUDIO_SHARING
 import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS
 import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE
 import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
@@ -61,6 +63,7 @@
     private val deviceItemInteractor: DeviceItemInteractor,
     private val bluetoothStateInteractor: BluetoothStateInteractor,
     private val bluetoothAutoOnInteractor: BluetoothAutoOnInteractor,
+    private val audioSharingInteractor: AudioSharingInteractor,
     private val dialogTransitionAnimator: DialogTransitionAnimator,
     private val activityStarter: ActivityStarter,
     private val uiEventLogger: UiEventLogger,
@@ -119,7 +122,8 @@
                                     dialog,
                                     it.take(MAX_DEVICE_ITEM_ENTRY),
                                     showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY,
-                                    showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled
+                                    showPairNewDevice =
+                                        bluetoothStateInteractor.isBluetoothEnabled()
                                 )
                                 animateProgressBar(dialog, false)
                             }
@@ -142,10 +146,25 @@
                     }
                     .launchIn(this)
 
+                if (BluetoothUtils.isAudioSharingEnabled()) {
+                    audioSharingInteractor.audioSharingButtonStateUpdate
+                        .onEach {
+                            if (it is AudioSharingButtonState.Visible) {
+                                dialogDelegate.onAudioSharingButtonUpdated(
+                                    dialog,
+                                    VISIBLE,
+                                    context.getString(it.resId)
+                                )
+                            } else {
+                                dialogDelegate.onAudioSharingButtonUpdated(dialog, GONE, null)
+                            }
+                        }
+                        .launchIn(this)
+                }
+
                 // bluetoothStateUpdate is emitted when bluetooth on/off state is changed, re-fetch
                 // the device item list.
                 bluetoothStateInteractor.bluetoothStateUpdate
-                    .filterNotNull()
                     .onEach {
                         dialogDelegate.onBluetoothStateUpdated(
                             dialog,
@@ -165,9 +184,10 @@
                 // bluetoothStateToggle is emitted when user toggles the bluetooth state switch,
                 // send the new value to the bluetoothStateInteractor and animate the progress bar.
                 dialogDelegate.bluetoothStateToggle
+                    .filterNotNull()
                     .onEach {
                         dialogDelegate.animateProgressBar(dialog, true)
-                        bluetoothStateInteractor.isBluetoothEnabled = it
+                        bluetoothStateInteractor.setBluetoothEnabled(it)
                     }
                     .launchIn(this)
 
@@ -222,11 +242,10 @@
 
         return bluetoothDialogDelegateFactory.create(
             UiProperties.build(
-                bluetoothStateInteractor.isBluetoothEnabled,
+                bluetoothStateInteractor.isBluetoothEnabled(),
                 isAutoOnToggleFeatureAvailable()
             ),
             cachedContentHeight,
-            bluetoothStateInteractor.isBluetoothEnabled,
             this@BluetoothTileDialogViewModel,
             { cancelJob() }
         )
@@ -256,6 +275,11 @@
         startSettingsActivity(Intent(ACTION_PAIR_NEW_DEVICE), view)
     }
 
+    override fun onAudioSharingButtonClicked(view: View) {
+        uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_AUDIO_SHARING_BUTTON_CLICKED)
+        startSettingsActivity(Intent(ACTION_AUDIO_SHARING), view)
+    }
+
     private fun cancelJob() {
         job?.cancel()
         job = null
@@ -312,4 +336,5 @@
     fun onDeviceItemGearClicked(deviceItem: DeviceItem, view: View)
     fun onSeeAllClicked(view: View)
     fun onPairNewDeviceClicked(view: View)
+    fun onAudioSharingButtonClicked(view: View)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
index dc5efef..0ea98d1 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
@@ -37,6 +37,7 @@
 
 enum class DeviceItemType {
     ACTIVE_MEDIA_BLUETOOTH_DEVICE,
+    AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
     AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
     CONNECTED_BLUETOOTH_DEVICE,
     SAVED_BLUETOOTH_DEVICE,
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index f04ba75..49d0847 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -21,13 +21,16 @@
 import android.media.AudioManager
 import com.android.settingslib.bluetooth.BluetoothUtils
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.settingslib.flags.Flags
+import com.android.settingslib.flags.Flags.enableLeAudioSharing
 import com.android.systemui.res.R
 
 private val backgroundOn = R.drawable.settingslib_switch_bar_bg_on
 private val backgroundOff = R.drawable.bluetooth_tile_dialog_bg_off
 private val backgroundOffBusy = R.drawable.bluetooth_tile_dialog_bg_off_busy
 private val connected = R.string.quick_settings_bluetooth_device_connected
+private val audioSharing = R.string.quick_settings_bluetooth_device_audio_sharing
 private val saved = R.string.quick_settings_bluetooth_device_saved
 private val actionAccessibilityLabelActivate =
     R.string.accessibility_quick_settings_bluetooth_device_tap_to_activate
@@ -39,35 +42,81 @@
     abstract fun isFilterMatched(
         context: Context,
         cachedDevice: CachedBluetoothDevice,
-        audioManager: AudioManager?
+        audioManager: AudioManager,
     ): Boolean
 
     abstract fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem
+
+    companion object {
+        @JvmStatic
+        fun createDeviceItem(
+            context: Context,
+            cachedDevice: CachedBluetoothDevice,
+            type: DeviceItemType,
+            connectionSummary: String,
+            background: Int,
+            actionAccessibilityLabel: String
+        ): DeviceItem {
+            return DeviceItem(
+                type = type,
+                cachedBluetoothDevice = cachedDevice,
+                deviceName = cachedDevice.name,
+                connectionSummary = connectionSummary,
+                iconWithDescription =
+                    BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let {
+                        Pair(it.first, it.second)
+                    },
+                background = background,
+                isEnabled = !cachedDevice.isBusy,
+                actionAccessibilityLabel = actionAccessibilityLabel
+            )
+        }
+    }
 }
 
 internal open class ActiveMediaDeviceItemFactory : DeviceItemFactory() {
     override fun isFilterMatched(
         context: Context,
         cachedDevice: CachedBluetoothDevice,
-        audioManager: AudioManager?
+        audioManager: AudioManager
     ): Boolean {
         return BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
             BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, audioManager)
     }
 
     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
-        return DeviceItem(
-            type = DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
-            cachedBluetoothDevice = cachedDevice,
-            deviceName = cachedDevice.name,
-            connectionSummary = cachedDevice.connectionSummary ?: "",
-            iconWithDescription =
-                BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
-                    Pair(p.first, p.second)
-                },
-            background = backgroundOn,
-            isEnabled = !cachedDevice.isBusy,
-            actionAccessibilityLabel = context.getString(actionAccessibilityLabelDisconnect),
+        return createDeviceItem(
+            context,
+            cachedDevice,
+            DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
+            cachedDevice.connectionSummary ?: "",
+            backgroundOn,
+            context.getString(actionAccessibilityLabelDisconnect)
+        )
+    }
+}
+
+internal class AudioSharingMediaDeviceItemFactory(
+    private val localBluetoothManager: LocalBluetoothManager?
+) : DeviceItemFactory() {
+    override fun isFilterMatched(
+        context: Context,
+        cachedDevice: CachedBluetoothDevice,
+        audioManager: AudioManager
+    ): Boolean {
+        return enableLeAudioSharing() &&
+            BluetoothUtils.hasConnectedBroadcastSource(cachedDevice, localBluetoothManager)
+    }
+
+    override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
+        return createDeviceItem(
+            context,
+            cachedDevice,
+            DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
+            cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
+                ?: context.getString(audioSharing),
+            if (cachedDevice.isBusy) backgroundOffBusy else backgroundOn,
+            ""
         )
     }
 }
@@ -76,7 +125,7 @@
     override fun isFilterMatched(
         context: Context,
         cachedDevice: CachedBluetoothDevice,
-        audioManager: AudioManager?
+        audioManager: AudioManager
     ): Boolean {
         return BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
             BluetoothUtils.isAvailableHearingDevice(cachedDevice)
@@ -87,27 +136,21 @@
     override fun isFilterMatched(
         context: Context,
         cachedDevice: CachedBluetoothDevice,
-        audioManager: AudioManager?
+        audioManager: AudioManager
     ): Boolean {
         return !BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
             BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, audioManager)
     }
 
-    // TODO(b/298124674): move create() to the abstract class to reduce duplicate code
     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
-        return DeviceItem(
-            type = DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
-            cachedBluetoothDevice = cachedDevice,
-            deviceName = cachedDevice.name,
-            connectionSummary = cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
-                    ?: context.getString(connected),
-            iconWithDescription =
-                BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
-                    Pair(p.first, p.second)
-                },
-            background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
-            isEnabled = !cachedDevice.isBusy,
-            actionAccessibilityLabel = context.getString(actionAccessibilityLabelActivate),
+        return createDeviceItem(
+            context,
+            cachedDevice,
+            DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
+            cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
+                ?: context.getString(connected),
+            if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
+            context.getString(actionAccessibilityLabelActivate)
         )
     }
 }
@@ -116,7 +159,7 @@
     override fun isFilterMatched(
         context: Context,
         cachedDevice: CachedBluetoothDevice,
-        audioManager: AudioManager?
+        audioManager: AudioManager
     ): Boolean {
         return !BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
             BluetoothUtils.isAvailableHearingDevice(cachedDevice)
@@ -127,32 +170,25 @@
     override fun isFilterMatched(
         context: Context,
         cachedDevice: CachedBluetoothDevice,
-        audioManager: AudioManager?
+        audioManager: AudioManager
     ): Boolean {
         return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
-            !BluetoothUtils.isExclusivelyManagedBluetoothDevice(
-                context,
-                cachedDevice.getDevice()
-            ) && BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
+            !BluetoothUtils.isExclusivelyManagedBluetoothDevice(context, cachedDevice.device) &&
+                BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
         } else {
             BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
         }
     }
 
     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
-        return DeviceItem(
-            type = DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
-            cachedBluetoothDevice = cachedDevice,
-            deviceName = cachedDevice.name,
-            connectionSummary = cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
-                    ?: context.getString(connected),
-            iconWithDescription =
-                BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
-                    Pair(p.first, p.second)
-                },
-            background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
-            isEnabled = !cachedDevice.isBusy,
-            actionAccessibilityLabel = context.getString(actionAccessibilityLabelDisconnect),
+        return createDeviceItem(
+            context,
+            cachedDevice,
+            DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
+            cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
+                ?: context.getString(connected),
+            if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
+            context.getString(actionAccessibilityLabelDisconnect)
         )
     }
 }
@@ -161,32 +197,26 @@
     override fun isFilterMatched(
         context: Context,
         cachedDevice: CachedBluetoothDevice,
-        audioManager: AudioManager?
+        audioManager: AudioManager
     ): Boolean {
         return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
-            !BluetoothUtils.isExclusivelyManagedBluetoothDevice(
-                context,
-                cachedDevice.getDevice()
-            ) && cachedDevice.bondState == BluetoothDevice.BOND_BONDED && !cachedDevice.isConnected
+            !BluetoothUtils.isExclusivelyManagedBluetoothDevice(context, cachedDevice.device) &&
+                cachedDevice.bondState == BluetoothDevice.BOND_BONDED &&
+                !cachedDevice.isConnected
         } else {
             cachedDevice.bondState == BluetoothDevice.BOND_BONDED && !cachedDevice.isConnected
         }
     }
 
     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
-        return DeviceItem(
-            type = DeviceItemType.SAVED_BLUETOOTH_DEVICE,
-            cachedBluetoothDevice = cachedDevice,
-            deviceName = cachedDevice.name,
-            connectionSummary = cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
-                    ?: context.getString(saved),
-            iconWithDescription =
-                BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
-                    Pair(p.first, p.second)
-                },
-            background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
-            isEnabled = !cachedDevice.isBusy,
-            actionAccessibilityLabel = context.getString(actionAccessibilityLabelActivate),
+        return createDeviceItem(
+            context,
+            cachedDevice,
+            DeviceItemType.SAVED_BLUETOOTH_DEVICE,
+            cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
+                ?: context.getString(saved),
+            if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
+            context.getString(actionAccessibilityLabelActivate)
         )
     }
 }
@@ -195,7 +225,7 @@
     override fun isFilterMatched(
         context: Context,
         cachedDevice: CachedBluetoothDevice,
-        audioManager: AudioManager?
+        audioManager: AudioManager
     ): Boolean {
         return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
             !BluetoothUtils.isExclusivelyManagedBluetoothDevice(
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
index 4e28caf..66e593b 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
@@ -113,6 +113,7 @@
     private var deviceItemFactoryList: List<DeviceItemFactory> =
         listOf(
             ActiveMediaDeviceItemFactory(),
+            AudioSharingMediaDeviceItemFactory(localBluetoothManager),
             AvailableMediaDeviceItemFactory(),
             ConnectedDeviceItemFactory(),
             SavedDeviceItemFactory()
@@ -121,6 +122,7 @@
     private var displayPriority: List<DeviceItemType> =
         listOf(
             DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
+            DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
             DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
             DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
             DeviceItemType.SAVED_BLUETOOTH_DEVICE,
@@ -177,6 +179,9 @@
                         disconnect()
                         uiEventLogger.log(BluetoothTileDialogUiEvent.ACTIVE_DEVICE_DISCONNECT)
                     }
+                    DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
+                        uiEventLogger.log(BluetoothTileDialogUiEvent.AUDIO_SHARING_DEVICE_CLICKED)
+                    }
                     DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> {
                         setActive()
                         uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
new file mode 100644
index 0000000..8a1a082
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
@@ -0,0 +1,171 @@
+/*
+ * 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.bluetooth.qsdialog
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class AudioSharingInteractorTest : SysuiTestCase() {
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+    private val bluetoothState = MutableStateFlow(false)
+    private val deviceItemUpdate: MutableSharedFlow<List<DeviceItem>> = MutableSharedFlow()
+    @Mock private lateinit var cachedBluetoothDevice: CachedBluetoothDevice
+    @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
+    @Mock private lateinit var bluetoothStateInteractor: BluetoothStateInteractor
+    @Mock private lateinit var deviceItemInteractor: DeviceItemInteractor
+    @Mock private lateinit var deviceItem: DeviceItem
+    private lateinit var mockitoSession: StaticMockitoSession
+    private lateinit var audioSharingInteractor: AudioSharingInteractor
+
+    @Before
+    fun setUp() {
+        mockitoSession =
+            mockitoSession().initMocks(this).mockStatic(BluetoothUtils::class.java).startMocking()
+        whenever(bluetoothStateInteractor.bluetoothStateUpdate).thenReturn(bluetoothState)
+        whenever(deviceItemInteractor.deviceItemUpdate).thenReturn(deviceItemUpdate)
+        audioSharingInteractor =
+            AudioSharingInteractor(
+                localBluetoothManager,
+                bluetoothStateInteractor,
+                deviceItemInteractor,
+                testScope.backgroundScope,
+                testDispatcher,
+            )
+    }
+
+    @After
+    fun tearDown() {
+        mockitoSession.finishMocking()
+    }
+
+    @Test
+    fun testButtonStateUpdate_bluetoothOff_returnGone() {
+        testScope.runTest {
+            val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+
+            assertThat(actual).isEqualTo(AudioSharingButtonState.Gone)
+        }
+    }
+
+    @Test
+    fun testButtonStateUpdate_noDevice_returnGone() {
+        testScope.runTest {
+            val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+            bluetoothState.value = true
+            runCurrent()
+
+            assertThat(actual).isEqualTo(AudioSharingButtonState.Gone)
+        }
+    }
+
+    @Test
+    fun testButtonStateUpdate_isBroadcasting_returnSharingAudio() {
+        testScope.runTest {
+            whenever(BluetoothUtils.isBroadcasting(localBluetoothManager)).thenReturn(true)
+
+            val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+            bluetoothState.value = true
+            deviceItemUpdate.emit(listOf())
+            runCurrent()
+
+            assertThat(actual)
+                .isEqualTo(
+                    AudioSharingButtonState.Visible(
+                        R.string.quick_settings_bluetooth_audio_sharing_button_sharing
+                    )
+                )
+        }
+    }
+
+    @Test
+    fun testButtonStateUpdate_hasSource_returnGone() {
+        testScope.runTest {
+            whenever(BluetoothUtils.isBroadcasting(localBluetoothManager)).thenReturn(false)
+            whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
+            whenever(
+                    BluetoothUtils.hasConnectedBroadcastSource(
+                        cachedBluetoothDevice,
+                        localBluetoothManager
+                    )
+                )
+                .thenReturn(true)
+
+            val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+            bluetoothState.value = true
+            deviceItemUpdate.emit(listOf(deviceItem))
+            runCurrent()
+
+            assertThat(actual).isEqualTo(AudioSharingButtonState.Gone)
+        }
+    }
+
+    @Test
+    fun testButtonStateUpdate_hasActiveDevice_returnAudioSharing() {
+        testScope.runTest {
+            whenever(BluetoothUtils.isBroadcasting(localBluetoothManager)).thenReturn(false)
+            whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
+            whenever(
+                    BluetoothUtils.hasConnectedBroadcastSource(
+                        cachedBluetoothDevice,
+                        localBluetoothManager
+                    )
+                )
+                .thenReturn(false)
+            whenever(BluetoothUtils.isActiveLeAudioDevice(cachedBluetoothDevice)).thenReturn(true)
+
+            val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+            bluetoothState.value = true
+            deviceItemUpdate.emit(listOf(deviceItem))
+            runCurrent()
+
+            assertThat(actual)
+                .isEqualTo(
+                    AudioSharingButtonState.Visible(
+                        R.string.quick_settings_bluetooth_audio_sharing_button
+                    )
+                )
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
index a8f82ed..6fe7d86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
@@ -23,6 +23,7 @@
 import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -41,7 +42,8 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class BluetoothStateInteractorTest : SysuiTestCase() {
     @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
-    private val testScope = TestScope()
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
 
     private lateinit var bluetoothStateInteractor: BluetoothStateInteractor
 
@@ -52,7 +54,12 @@
     @Before
     fun setUp() {
         bluetoothStateInteractor =
-            BluetoothStateInteractor(localBluetoothManager, logger, testScope.backgroundScope)
+            BluetoothStateInteractor(
+                localBluetoothManager,
+                logger,
+                testScope.backgroundScope,
+                testDispatcher
+            )
         `when`(localBluetoothManager.bluetoothAdapter).thenReturn(bluetoothAdapter)
     }
 
@@ -61,7 +68,7 @@
         testScope.runTest {
             `when`(bluetoothAdapter.isEnabled).thenReturn(true)
 
-            assertThat(bluetoothStateInteractor.isBluetoothEnabled).isTrue()
+            assertThat(bluetoothStateInteractor.isBluetoothEnabled()).isTrue()
         }
     }
 
@@ -70,7 +77,7 @@
         testScope.runTest {
             `when`(bluetoothAdapter.isEnabled).thenReturn(false)
 
-            assertThat(bluetoothStateInteractor.isBluetoothEnabled).isFalse()
+            assertThat(bluetoothStateInteractor.isBluetoothEnabled()).isFalse()
         }
     }
 
@@ -79,7 +86,7 @@
         testScope.runTest {
             `when`(bluetoothAdapter.isEnabled).thenReturn(false)
 
-            bluetoothStateInteractor.isBluetoothEnabled = true
+            bluetoothStateInteractor.setBluetoothEnabled(true)
             verify(bluetoothAdapter).enable()
             verify(logger)
                 .logBluetoothState(BluetoothStateStage.BLUETOOTH_STATE_VALUE_SET, true.toString())
@@ -91,7 +98,7 @@
         testScope.runTest {
             `when`(bluetoothAdapter.isEnabled).thenReturn(false)
 
-            bluetoothStateInteractor.isBluetoothEnabled = false
+            bluetoothStateInteractor.setBluetoothEnabled(false)
             verify(bluetoothAdapter, never()).enable()
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
index 12dfe97..62c98b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
@@ -110,7 +110,6 @@
             BluetoothTileDialogDelegate(
                 uiProperties,
                 CONTENT_HEIGHT,
-                ENABLED,
                 bluetoothTileDialogCallback,
                 {},
                 dispatcher,
@@ -211,7 +210,6 @@
             BluetoothTileDialogDelegate(
                     uiProperties,
                     CONTENT_HEIGHT,
-                    ENABLED,
                     bluetoothTileDialogCallback,
                     {},
                     dispatcher,
@@ -267,7 +265,6 @@
                 BluetoothTileDialogDelegate(
                         BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
                         cachedHeight,
-                        ENABLED,
                         bluetoothTileDialogCallback,
                         {},
                         dispatcher,
@@ -291,7 +288,6 @@
                 BluetoothTileDialogDelegate(
                         BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
                         MATCH_PARENT,
-                        ENABLED,
                         bluetoothTileDialogCallback,
                         {},
                         dispatcher,
@@ -315,7 +311,6 @@
                 BluetoothTileDialogDelegate(
                         BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
                         MATCH_PARENT,
-                        ENABLED,
                         bluetoothTileDialogCallback,
                         {},
                         dispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
index 6d99c5b..b05d959 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
@@ -52,7 +52,6 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.anyBoolean
@@ -74,7 +73,7 @@
 
     @Mock private lateinit var bluetoothStateInteractor: BluetoothStateInteractor
 
-    @Mock private lateinit var bluetoothAutoOnInteractor: BluetoothAutoOnInteractor
+    @Mock private lateinit var audioSharingInteractor: AudioSharingInteractor
 
     @Mock private lateinit var deviceItemInteractor: DeviceItemInteractor
 
@@ -92,6 +91,8 @@
 
     @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
 
+    @Mock private lateinit var bluetoothTileDialogLogger: BluetoothTileDialogLogger
+
     @Mock
     private lateinit var mBluetoothTileDialogDelegateDelegateFactory:
         BluetoothTileDialogDelegate.Factory
@@ -115,7 +116,12 @@
         bluetoothTileDialogViewModel =
             BluetoothTileDialogViewModel(
                 deviceItemInteractor,
-                bluetoothStateInteractor,
+                BluetoothStateInteractor(
+                    localBluetoothManager,
+                    bluetoothTileDialogLogger,
+                    testScope.backgroundScope,
+                    dispatcher
+                ),
                 // TODO(b/316822488): Create FakeBluetoothAutoOnInteractor.
                 BluetoothAutoOnInteractor(
                     BluetoothAutoOnRepository(
@@ -125,6 +131,7 @@
                         dispatcher
                     )
                 ),
+                audioSharingInteractor,
                 mDialogTransitionAnimator,
                 activityStarter,
                 uiEventLogger,
@@ -135,20 +142,9 @@
                 mBluetoothTileDialogDelegateDelegateFactory
             )
         whenever(deviceItemInteractor.deviceItemUpdate).thenReturn(MutableSharedFlow())
-        whenever(bluetoothStateInteractor.bluetoothStateUpdate)
-            .thenReturn(MutableStateFlow(null).asStateFlow())
         whenever(deviceItemInteractor.deviceItemUpdateRequest)
             .thenReturn(MutableStateFlow(Unit).asStateFlow())
-        whenever(bluetoothStateInteractor.isBluetoothEnabled).thenReturn(true)
-        whenever(
-                mBluetoothTileDialogDelegateDelegateFactory.create(
-                    any(),
-                    anyInt(),
-                    ArgumentMatchers.anyBoolean(),
-                    any(),
-                    any()
-                )
-            )
+        whenever(mBluetoothTileDialogDelegateDelegateFactory.create(any(), anyInt(), any(), any()))
             .thenReturn(bluetoothTileDialogDelegate)
         whenever(bluetoothTileDialogDelegate.createDialog()).thenReturn(sysuiDialog)
         whenever(sysuiDialog.context).thenReturn(mContext)
@@ -159,6 +155,8 @@
         whenever(bluetoothTileDialogDelegate.contentHeight).thenReturn(getMutableStateFlow(0))
         whenever(bluetoothTileDialogDelegate.bluetoothAutoOnToggle)
             .thenReturn(getMutableStateFlow(false))
+        whenever(audioSharingInteractor.audioSharingButtonStateUpdate)
+            .thenReturn(getMutableStateFlow(AudioSharingButtonState.Gone))
     }
 
     @Test
@@ -201,15 +199,6 @@
     }
 
     @Test
-    fun testShowDialog_withBluetoothStateValue() {
-        testScope.runTest {
-            bluetoothTileDialogViewModel.showDialog(null)
-
-            verify(bluetoothStateInteractor).bluetoothStateUpdate
-        }
-    }
-
-    @Test
     fun testStartSettingsActivity_activityLaunched_dialogDismissed() {
         testScope.runTest {
             whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
index eb735cb..daf4a3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -281,7 +281,7 @@
             override fun isFilterMatched(
                 context: Context,
                 cachedDevice: CachedBluetoothDevice,
-                audioManager: AudioManager?
+                audioManager: AudioManager
             ) = isFilterMatchFunc(cachedDevice)
 
             override fun create(context: Context, cachedDevice: CachedBluetoothDevice) = deviceItem