[Audiosharing] Add device volume controls.

Flagged with enable_le_audio_sharing

Bug: 305620450
Test: Manual
Change-Id: I5330cf3b3b4497af22820657b61279070f9c4af1
diff --git a/res/xml/bluetooth_audio_sharing.xml b/res/xml/bluetooth_audio_sharing.xml
index 681c768..d5e08bb 100644
--- a/res/xml/bluetooth_audio_sharing.xml
+++ b/res/xml/bluetooth_audio_sharing.xml
@@ -19,18 +19,23 @@
     xmlns:settings="http://schemas.android.com/apk/res-auto"
     android:title="@string/audio_sharing_title">
 
+    <PreferenceCategory
+        android:key="audio_sharing_device_volume_group"
+        android:title="Devices listening"
+        settings:controller="com.android.settings.connecteddevice.audiosharing.AudioSharingDeviceVolumeGroupController" />
+
     <Preference
-        android:key="calls_and_alarms"
-        android:title="@string/calls_and_alarms_device_title"
         android:icon="@drawable/ic_audio_calls_and_alarms"
-        settings:controller="com.android.settings.connecteddevice.audiosharing.CallsAndAlarmsPreferenceController"
-        android:summary=""/>
+        android:key="calls_and_alarms"
+        android:summary=""
+        android:title="@string/calls_and_alarms_device_title"
+        settings:controller="com.android.settings.connecteddevice.audiosharing.CallsAndAlarmsPreferenceController" />
 
     <com.android.settings.connecteddevice.audiosharing.AudioSharingNamePreference
         android:key="audio_sharing_stream_name"
-        android:title="Stream name"
         android:summary="********"
-        settings:controller="com.android.settings.connecteddevice.audiosharing.AudioSharingNamePreferenceController"/>
+        android:title="Stream name"
+        settings:controller="com.android.settings.connecteddevice.audiosharing.AudioSharingNamePreferenceController" />
 
     <PreferenceCategory
         android:key="audio_streams_settings_category"
@@ -38,10 +43,10 @@
         settings:controller="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsCategoryController">
 
         <Preference
-            android:key="audio_streams_settings"
             android:fragment="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsDashboardFragment"
-            android:title="@string/audio_streams_pref_title"
-            android:icon="@drawable/ic_chevron_right_24dp" />
+            android:icon="@drawable/ic_chevron_right_24dp"
+            android:key="audio_streams_settings"
+            android:title="@string/audio_streams_pref_title" />
 
     </PreferenceCategory>
 </PreferenceScreen>
\ No newline at end of file
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBluetoothDeviceUpdater.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBluetoothDeviceUpdater.java
index d3908c2..59393ad 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBluetoothDeviceUpdater.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBluetoothDeviceUpdater.java
@@ -16,7 +16,6 @@
 
 package com.android.settings.connecteddevice.audiosharing;
 
-import android.bluetooth.BluetoothLeBroadcastReceiveState;
 import android.content.Context;
 import android.util.Log;
 
@@ -27,11 +26,8 @@
 import com.android.settings.bluetooth.Utils;
 import com.android.settings.connecteddevice.DevicePreferenceCallback;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 
-import java.util.List;
-
 public class AudioSharingBluetoothDeviceUpdater extends BluetoothDeviceUpdater
         implements Preference.OnPreferenceClickListener {
 
@@ -55,7 +51,8 @@
         if (isDeviceConnected(cachedDevice) && isDeviceInCachedDevicesList(cachedDevice)) {
             // If device is LE audio device and has a broadcast source,
             // it would show in audio sharing devices group.
-            if (cachedDevice.isConnectedLeAudioDevice() && hasBroadcastSource(cachedDevice)) {
+            if (cachedDevice.isConnectedLeAudioDevice()
+                    && AudioSharingUtils.hasBroadcastSource(cachedDevice, mLocalBluetoothManager)) {
                 isFilterMatched = true;
             }
         }
@@ -76,24 +73,6 @@
         return device.setActive();
     }
 
-    private boolean hasBroadcastSource(CachedBluetoothDevice cachedDevice) {
-        LocalBluetoothLeBroadcastAssistant assistant =
-                mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
-        if (assistant == null) {
-            return false;
-        }
-        List<BluetoothLeBroadcastReceiveState> sourceList =
-                assistant.getAllSources(cachedDevice.getDevice());
-        if (!sourceList.isEmpty()) return true;
-        // Return true if member device is in broadcast.
-        for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) {
-            List<BluetoothLeBroadcastReceiveState> list =
-                    assistant.getAllSources(device.getDevice());
-            if (!list.isEmpty()) return true;
-        }
-        return false;
-    }
-
     @Override
     protected String getPreferenceKey() {
         return PREF_KEY;
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
index 40207be..cf5881b 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
@@ -31,6 +31,7 @@
 
     SettingsMainSwitchBar mMainSwitchBar;
     private AudioSharingSwitchBarController mSwitchBarController;
+    private AudioSharingDeviceVolumeGroupController mAudioSharingDeviceVolumeGroupController;
     private CallsAndAlarmsPreferenceController mCallsAndAlarmsPreferenceController;
     private AudioSharingNamePreferenceController mAudioSharingNamePreferenceController;
 
@@ -66,6 +67,9 @@
     @Override
     public void onAttach(Context context) {
         super.onAttach(context);
+        mAudioSharingDeviceVolumeGroupController =
+                use(AudioSharingDeviceVolumeGroupController.class);
+        mAudioSharingDeviceVolumeGroupController.init(this);
         mCallsAndAlarmsPreferenceController = use(CallsAndAlarmsPreferenceController.class);
         mCallsAndAlarmsPreferenceController.init(this);
         mAudioSharingNamePreferenceController = use(AudioSharingNamePreferenceController.class);
@@ -91,6 +95,7 @@
     }
 
     private void updateVisibilityForAttachedPreferences(boolean isVisible) {
+        mAudioSharingDeviceVolumeGroupController.updateVisibility(isVisible);
         mCallsAndAlarmsPreferenceController.updateVisibility(isVisible);
         mAudioSharingNamePreferenceController.updateVisibility(isVisible);
     }
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdater.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdater.java
new file mode 100644
index 0000000..0668f50
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdater.java
@@ -0,0 +1,139 @@
+/*
+ * 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.settings.connecteddevice.audiosharing;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.util.Log;
+import android.widget.SeekBar;
+
+import androidx.preference.Preference;
+
+import com.android.settings.bluetooth.BluetoothDevicePreference;
+import com.android.settings.bluetooth.BluetoothDeviceUpdater;
+import com.android.settings.bluetooth.Utils;
+import com.android.settings.connecteddevice.DevicePreferenceCallback;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+public class AudioSharingDeviceVolumeControlUpdater extends BluetoothDeviceUpdater
+        implements Preference.OnPreferenceClickListener {
+
+    private static final String TAG = "AudioSharingDeviceVolumeControlUpdater";
+
+    private static final String PREF_KEY = "audio_sharing_volume_control";
+
+    private final LocalBluetoothManager mLocalBtManager;
+
+    public AudioSharingDeviceVolumeControlUpdater(
+            Context context,
+            DevicePreferenceCallback devicePreferenceCallback,
+            int metricsCategory) {
+        super(context, devicePreferenceCallback, metricsCategory);
+        mLocalBtManager = Utils.getLocalBluetoothManager(context);
+    }
+
+    @Override
+    public boolean isFilterMatched(CachedBluetoothDevice cachedDevice) {
+        boolean isFilterMatched = false;
+        if (isDeviceConnected(cachedDevice) && isDeviceInCachedDevicesList(cachedDevice)) {
+            // If device is LE audio device and has a broadcast source,
+            // it would show in audio sharing devices group.
+            if (cachedDevice.isConnectedLeAudioDevice()
+                    && AudioSharingUtils.hasBroadcastSource(cachedDevice, mLocalBtManager)) {
+                isFilterMatched = true;
+            }
+        }
+        Log.d(
+                TAG,
+                "isFilterMatched() device : "
+                        + cachedDevice.getName()
+                        + ", isFilterMatched : "
+                        + isFilterMatched);
+        return isFilterMatched;
+    }
+
+    @Override
+    public boolean onPreferenceClick(Preference preference) {
+        return true;
+    }
+
+    @Override
+    protected void addPreference(CachedBluetoothDevice cachedDevice) {
+        if (cachedDevice == null) return;
+        final BluetoothDevice device = cachedDevice.getDevice();
+        if (!mPreferenceMap.containsKey(device)) {
+            SeekBar.OnSeekBarChangeListener listener =
+                    new SeekBar.OnSeekBarChangeListener() {
+                        @Override
+                        public void onProgressChanged(
+                                SeekBar seekBar, int progress, boolean fromUser) {}
+
+                        @Override
+                        public void onStartTrackingTouch(SeekBar seekBar) {}
+
+                        @Override
+                        public void onStopTrackingTouch(SeekBar seekBar) {
+                            if (mLocalBtManager != null
+                                    && mLocalBtManager.getProfileManager().getVolumeControlProfile()
+                                            != null) {
+                                mLocalBtManager
+                                        .getProfileManager()
+                                        .getVolumeControlProfile()
+                                        .setDeviceVolume(
+                                                cachedDevice.getDevice(),
+                                                seekBar.getProgress(),
+                                                /* isGroupOp= */ true);
+                            }
+                        }
+                    };
+            AudioSharingDeviceVolumePreference vPreference =
+                    new AudioSharingDeviceVolumePreference(mPrefContext, cachedDevice, listener);
+            vPreference.setKey(getPreferenceKey());
+            vPreference.setIcon(com.android.settingslib.R.drawable.ic_bt_untethered_earbuds);
+            vPreference.setTitle(cachedDevice.getName());
+            mPreferenceMap.put(device, vPreference);
+            mDevicePreferenceCallback.onDeviceAdded(vPreference);
+        }
+    }
+
+    @Override
+    protected String getPreferenceKey() {
+        return PREF_KEY;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return TAG;
+    }
+
+    @Override
+    protected void update(CachedBluetoothDevice cachedBluetoothDevice) {
+        super.update(cachedBluetoothDevice);
+        Log.d(TAG, "Map : " + mPreferenceMap);
+    }
+
+    @Override
+    protected void addPreference(
+            CachedBluetoothDevice cachedDevice, @BluetoothDevicePreference.SortType int type) {}
+
+    @Override
+    protected void launchDeviceDetails(Preference preference) {}
+
+    @Override
+    public void refreshPreference() {}
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java
new file mode 100644
index 0000000..90054d4
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java
@@ -0,0 +1,278 @@
+/*
+ * 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.settings.connecteddevice.audiosharing;
+
+import android.annotation.IntRange;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastAssistant;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothVolumeControl;
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.bluetooth.BluetoothDeviceUpdater;
+import com.android.settings.bluetooth.Utils;
+import com.android.settings.connecteddevice.DevicePreferenceCallback;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.VolumeControlProfile;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+public class AudioSharingDeviceVolumeGroupController extends AudioSharingBasePreferenceController
+        implements DefaultLifecycleObserver, DevicePreferenceCallback {
+
+    private static final String TAG = "AudioSharingDeviceVolumeGroupController";
+    private static final String KEY = "audio_sharing_device_volume_group";
+
+    private final LocalBluetoothManager mLocalBtManager;
+    private final LocalBluetoothLeBroadcastAssistant mAssistant;
+    private final Executor mExecutor;
+    private VolumeControlProfile mVolumeControl;
+    private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
+    private FragmentManager mFragmentManager;
+    private PreferenceGroup mPreferenceGroup;
+    private Map<Preference, BluetoothVolumeControl.Callback> mCallbackMap =
+            new HashMap<Preference, BluetoothVolumeControl.Callback>();
+
+    private BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
+            new BluetoothLeBroadcastAssistant.Callback() {
+                @Override
+                public void onSearchStarted(int reason) {}
+
+                @Override
+                public void onSearchStartFailed(int reason) {}
+
+                @Override
+                public void onSearchStopped(int reason) {}
+
+                @Override
+                public void onSearchStopFailed(int reason) {}
+
+                @Override
+                public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {}
+
+                @Override
+                public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+                    Log.d(
+                            TAG,
+                            "onSourceAdded(), sink = "
+                                    + sink
+                                    + ", sourceId = "
+                                    + sourceId
+                                    + ", reason = "
+                                    + reason);
+                    mBluetoothDeviceUpdater.forceUpdate();
+                }
+
+                @Override
+                public void onSourceAddFailed(
+                        @NonNull BluetoothDevice sink,
+                        @NonNull BluetoothLeBroadcastMetadata source,
+                        int reason) {
+                    Log.d(
+                            TAG,
+                            "onSourceAddFailed(), sink = "
+                                    + sink
+                                    + ", source = "
+                                    + source
+                                    + ", reason = "
+                                    + reason);
+                }
+
+                @Override
+                public void onSourceModified(
+                        @NonNull BluetoothDevice sink, int sourceId, int reason) {}
+
+                @Override
+                public void onSourceModifyFailed(
+                        @NonNull BluetoothDevice sink, int sourceId, int reason) {}
+
+                @Override
+                public void onSourceRemoved(
+                        @NonNull BluetoothDevice sink, int sourceId, int reason) {
+                    Log.d(
+                            TAG,
+                            "onSourceRemoved(), sink = "
+                                    + sink
+                                    + ", sourceId = "
+                                    + sourceId
+                                    + ", reason = "
+                                    + reason);
+                    mBluetoothDeviceUpdater.forceUpdate();
+                }
+
+                @Override
+                public void onSourceRemoveFailed(
+                        @NonNull BluetoothDevice sink, int sourceId, int reason) {
+                    Log.d(
+                            TAG,
+                            "onSourceRemoveFailed(), sink = "
+                                    + sink
+                                    + ", sourceId = "
+                                    + sourceId
+                                    + ", reason = "
+                                    + reason);
+                }
+
+                @Override
+                public void onReceiveStateChanged(
+                        BluetoothDevice sink,
+                        int sourceId,
+                        BluetoothLeBroadcastReceiveState state) {}
+            };
+
+    public AudioSharingDeviceVolumeGroupController(Context context) {
+        super(context, KEY);
+        mLocalBtManager = Utils.getLocalBtManager(mContext);
+        mAssistant = mLocalBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+        mExecutor = Executors.newSingleThreadExecutor();
+        if (mLocalBtManager != null) {
+            mVolumeControl = mLocalBtManager.getProfileManager().getVolumeControlProfile();
+        }
+    }
+
+    @Override
+    public void onStart(@NonNull LifecycleOwner owner) {
+        if (mAssistant == null) {
+            Log.d(TAG, "onStart() Broadcast or assistant is not supported on this device");
+            return;
+        }
+        if (mBluetoothDeviceUpdater == null) {
+            Log.d(TAG, "onStart() Bluetooth device updater is not initialized");
+            return;
+        }
+        mAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
+        mBluetoothDeviceUpdater.registerCallback();
+    }
+
+    @Override
+    public void onStop(@NonNull LifecycleOwner owner) {
+        if (mAssistant == null) {
+            Log.d(TAG, "onStop() Broadcast or assistant is not supported on this device");
+            return;
+        }
+        if (mBluetoothDeviceUpdater == null) {
+            Log.d(TAG, "onStop() Bluetooth device updater is not initialized");
+            return;
+        }
+        mAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
+        mBluetoothDeviceUpdater.unregisterCallback();
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+
+        mPreferenceGroup = screen.findPreference(KEY);
+        mPreferenceGroup.setVisible(false);
+
+        if (isAvailable() && mBluetoothDeviceUpdater != null) {
+            mBluetoothDeviceUpdater.setPrefContext(screen.getContext());
+            mBluetoothDeviceUpdater.forceUpdate();
+        }
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY;
+    }
+
+    @Override
+    public void onDeviceAdded(Preference preference) {
+        if (mPreferenceGroup.getPreferenceCount() == 0) {
+            mPreferenceGroup.setVisible(true);
+        }
+        mPreferenceGroup.addPreference(preference);
+        if (mVolumeControl != null && preference instanceof AudioSharingDeviceVolumePreference) {
+            BluetoothVolumeControl.Callback callback =
+                    buildVcCallback((AudioSharingDeviceVolumePreference) preference);
+            mCallbackMap.put(preference, callback);
+            mVolumeControl.registerCallback(mExecutor, callback);
+        }
+    }
+
+    @Override
+    public void onDeviceRemoved(Preference preference) {
+        mPreferenceGroup.removePreference(preference);
+        if (mPreferenceGroup.getPreferenceCount() == 0) {
+            mPreferenceGroup.setVisible(false);
+        }
+        if (mVolumeControl != null && mCallbackMap.containsKey(preference)) {
+            mVolumeControl.unregisterCallback(mCallbackMap.get(preference));
+            mCallbackMap.remove(preference);
+        }
+    }
+
+    @Override
+    public void updateVisibility(boolean isVisible) {
+        super.updateVisibility(isVisible);
+        if (mPreferenceGroup != null) {
+            mPreferenceGroup.setVisible(mPreferenceGroup.getPreferenceCount() > 0);
+        }
+    }
+
+    /**
+     * Initialize the controller.
+     *
+     * @param fragment The fragment to provide the context and metrics category for {@link
+     *     AudioSharingBluetoothDeviceUpdater} and provide the host for dialogs.
+     */
+    public void init(DashboardFragment fragment) {
+        mBluetoothDeviceUpdater =
+                new AudioSharingDeviceVolumeControlUpdater(
+                        fragment.getContext(),
+                        AudioSharingDeviceVolumeGroupController.this,
+                        fragment.getMetricsCategory());
+    }
+
+    private BluetoothVolumeControl.Callback buildVcCallback(
+            AudioSharingDeviceVolumePreference preference) {
+        return new BluetoothVolumeControl.Callback() {
+            @Override
+            public void onVolumeOffsetChanged(BluetoothDevice device, int volumeOffset) {}
+
+            @Override
+            public void onDeviceVolumeChanged(
+                    @android.annotation.NonNull BluetoothDevice device,
+                    @IntRange(from = 0, to = 255) int volume) {
+                Log.d(TAG, "onDeviceVolumeChanged changed " + device.getAnonymizedAddress());
+                CachedBluetoothDevice cachedDevice =
+                        mLocalBtManager.getCachedDeviceManager().findDevice(device);
+                if (cachedDevice == null) return;
+                if (preference.getCachedDevice() != null
+                        && preference.getCachedDevice().getGroupId() == cachedDevice.getGroupId()) {
+                    preference.setProgress(volume);
+                }
+            }
+        };
+    }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumePreference.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumePreference.java
new file mode 100644
index 0000000..3808b60
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumePreference.java
@@ -0,0 +1,73 @@
+/*
+ * 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.settings.connecteddevice.audiosharing;
+
+import android.content.Context;
+import android.widget.SeekBar;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.R;
+import com.android.settings.bluetooth.Utils;
+import com.android.settings.widget.SeekBarPreference;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+public class AudioSharingDeviceVolumePreference extends SeekBarPreference {
+    private static final String TAG = "AudioSharingDeviceVolumePreference";
+    public static final int MIN_VOLUME = 0;
+    public static final int MAX_VOLUME = 255;
+
+    protected SeekBar mSeekBar;
+    private final LocalBluetoothManager mLocalBtManager;
+    private final CachedBluetoothDevice mCachedDevice;
+    private final SeekBar.OnSeekBarChangeListener mListener;
+
+    public AudioSharingDeviceVolumePreference(
+            Context context,
+            @NonNull CachedBluetoothDevice device,
+            SeekBar.OnSeekBarChangeListener listener) {
+        super(context);
+        setLayoutResource(R.layout.preference_volume_slider);
+        mLocalBtManager = Utils.getLocalBtManager(context);
+        mCachedDevice = device;
+        mListener = listener;
+    }
+
+    @Nullable
+    public CachedBluetoothDevice getCachedDevice() {
+        return mCachedDevice;
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder view) {
+        super.onBindViewHolder(view);
+        mSeekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);
+        mSeekBar.setMax(MAX_VOLUME);
+        mSeekBar.setMin(MIN_VOLUME);
+        mSeekBar.setOnSeekBarChangeListener(mListener);
+    }
+
+    /** Set the progress bar to target progress */
+    public void setProgress(int progress) {
+        if (mSeekBar != null) {
+            mSeekBar.setProgress(progress);
+        }
+    }
+}