• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.connecteddevice.audiosharing;
18 
19 import android.app.settings.SettingsEnums;
20 import android.bluetooth.BluetoothCsipSetCoordinator;
21 import android.bluetooth.BluetoothDevice;
22 import android.content.Context;
23 import android.media.AudioManager;
24 import android.util.Log;
25 import android.widget.SeekBar;
26 
27 import androidx.annotation.NonNull;
28 import androidx.annotation.Nullable;
29 
30 import com.android.settings.R;
31 import com.android.settings.bluetooth.Utils;
32 import com.android.settings.overlay.FeatureFactory;
33 import com.android.settings.widget.SeekBarPreference;
34 import com.android.settingslib.bluetooth.BluetoothUtils;
35 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
36 import com.android.settingslib.bluetooth.LocalBluetoothManager;
37 import com.android.settingslib.bluetooth.VolumeControlProfile;
38 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
39 import com.android.settingslib.utils.ThreadUtils;
40 
41 public class AudioSharingDeviceVolumePreference extends SeekBarPreference {
42     private static final String TAG = "AudioSharingVolPref";
43 
44     public static final int MIN_VOLUME = 0;
45     public static final int MAX_VOLUME = 255;
46 
47     private final Context mContext;
48     private final CachedBluetoothDevice mCachedDevice;
49     @Nullable private final LocalBluetoothManager mBtManager;
50     @Nullable protected SeekBar mSeekBar;
51     private Boolean mTrackingTouch = false;
52     private MetricsFeatureProvider mMetricsFeatureProvider =
53             FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
54 
AudioSharingDeviceVolumePreference( Context context, @NonNull CachedBluetoothDevice device)55     public AudioSharingDeviceVolumePreference(
56             Context context, @NonNull CachedBluetoothDevice device) {
57         super(context);
58         setLayoutResource(R.layout.preference_volume_slider);
59         mContext = context;
60         mCachedDevice = device;
61         mBtManager = Utils.getLocalBtManager(mContext);
62     }
63 
64     @NonNull
getCachedDevice()65     public CachedBluetoothDevice getCachedDevice() {
66         return mCachedDevice;
67     }
68 
69     /**
70      * Initialize {@link AudioSharingDeviceVolumePreference}.
71      *
72      * <p>Need to be called after creating the preference.
73      */
initialize()74     public void initialize() {
75         setMax(MAX_VOLUME);
76         setMin(MIN_VOLUME);
77     }
78 
79     @Override
onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)80     public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
81         super.onProgressChanged(seekBar, progress, fromUser);
82         // When user use talk back swipe up/down or use Switch Access to change the volume bar
83         // progress, there is no onStopTrackingTouch triggered. So we need to check this scenario
84         // and update the device volume here.
85         if (fromUser && !mTrackingTouch) {
86             Log.d(TAG, "onProgressChanged from user and not in touch, handleProgressChange.");
87             handleProgressChange(progress);
88         }
89     }
90 
91     @Override
onStartTrackingTouch(SeekBar seekBar)92     public void onStartTrackingTouch(SeekBar seekBar) {
93         mTrackingTouch = true;
94         super.onStartTrackingTouch(seekBar);
95     }
96 
97     @Override
onStopTrackingTouch(SeekBar seekBar)98     public void onStopTrackingTouch(SeekBar seekBar) {
99         mTrackingTouch = false;
100         super.onStopTrackingTouch(seekBar);
101         // When user touch the volume bar to change volume, we only update the device volume when
102         // user stop touching the bar.
103         Log.d(TAG, "onStopTrackingTouch, handleProgressChange.");
104         handleProgressChange(seekBar.getProgress());
105     }
106 
107     @Override
equals(@ullable Object o)108     public boolean equals(@Nullable Object o) {
109         if ((o == null) || !(o instanceof AudioSharingDeviceVolumePreference)) {
110             return false;
111         }
112         return mCachedDevice.equals(
113                 ((AudioSharingDeviceVolumePreference) o).mCachedDevice);
114     }
115 
116     @Override
hashCode()117     public int hashCode() {
118         return mCachedDevice.hashCode();
119     }
120 
121     @Override
122     @NonNull
toString()123     public String toString() {
124         StringBuilder builder = new StringBuilder("Preference{");
125         builder.append("preference=").append(super.toString());
126         if (mCachedDevice.getDevice() != null) {
127             builder.append(", device=").append(mCachedDevice.getDevice().getAnonymizedAddress());
128         }
129         builder.append("}");
130         return builder.toString();
131     }
132 
onPreferenceAttributesChanged()133     void onPreferenceAttributesChanged() {
134         var unused = ThreadUtils.postOnBackgroundThread(() -> {
135             String name = mCachedDevice.getName();
136             AudioSharingUtils.postOnMainThread(mContext, () -> setTitle(name));
137         });
138     }
139 
handleProgressChange(int progress)140     private void handleProgressChange(int progress) {
141         var unused =
142                 ThreadUtils.postOnBackgroundThread(
143                         () -> {
144                             int groupId = BluetoothUtils.getGroupId(mCachedDevice);
145                             if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID
146                                     && groupId
147                                             == BluetoothUtils.getPrimaryGroupIdForBroadcast(
148                                                     mContext.getContentResolver(), mBtManager)) {
149                                 // Set media stream volume for primary buds, audio manager will
150                                 // update all buds volume in the audio sharing.
151                                 setAudioManagerStreamVolume(progress);
152                             } else {
153                                 // Set buds volume for other buds.
154                                 setDeviceVolume(mCachedDevice.getDevice(), progress);
155                             }
156                         });
157     }
158 
setDeviceVolume(@ullable BluetoothDevice device, int progress)159     private void setDeviceVolume(@Nullable BluetoothDevice device, int progress) {
160         if (device == null) {
161             Log.d(TAG, "Skip set device volume, device is null");
162             return;
163         }
164         VolumeControlProfile vc = mBtManager == null ? null
165                 : mBtManager.getProfileManager().getVolumeControlProfile();
166         if (vc != null) {
167             vc.setDeviceVolume(device, progress, /* isGroupOp= */ true);
168             mMetricsFeatureProvider.action(
169                     mContext,
170                     SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME,
171                     /* isPrimary= */ false);
172             Log.d(
173                     TAG,
174                     "set device volume, device = "
175                             + device.getAnonymizedAddress()
176                             + " volume = "
177                             + progress);
178         }
179     }
180 
setAudioManagerStreamVolume(int progress)181     private void setAudioManagerStreamVolume(int progress) {
182         int seekbarRange =
183                 AudioSharingDeviceVolumePreference.MAX_VOLUME
184                         - AudioSharingDeviceVolumePreference.MIN_VOLUME;
185         try {
186             AudioManager audioManager = mContext.getSystemService(AudioManager.class);
187             int streamVolumeRange =
188                     audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
189                             - audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC);
190             int volume = Math.round((float) progress * streamVolumeRange / seekbarRange);
191             audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
192             mMetricsFeatureProvider.action(
193                     mContext,
194                     SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME,
195                     /* isPrimary= */ true);
196             Log.d(TAG, "set music stream volume, volume = " + progress);
197         } catch (RuntimeException e) {
198             Log.e(TAG, "Fail to setAudioManagerStreamVolumeForFallbackDevice, error = " + e);
199         }
200     }
201 }
202