• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 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 package com.android.server.audio;
17 
18 import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_DEFAULT;
19 import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET;
20 import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_WATCH;
21 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_CARKIT;
22 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES;
23 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEARING_AID;
24 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_RECEIVER;
25 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER;
26 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN;
27 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_WATCH;
28 
29 import static com.android.media.audio.Flags.optimizeBtDeviceSwitch;
30 
31 import android.annotation.NonNull;
32 import android.annotation.Nullable;
33 import android.bluetooth.BluetoothA2dp;
34 import android.bluetooth.BluetoothAdapter;
35 import android.bluetooth.BluetoothClass;
36 import android.bluetooth.BluetoothCodecConfig;
37 import android.bluetooth.BluetoothCodecStatus;
38 import android.bluetooth.BluetoothDevice;
39 import android.bluetooth.BluetoothHeadset;
40 import android.bluetooth.BluetoothHearingAid;
41 import android.bluetooth.BluetoothLeAudio;
42 import android.bluetooth.BluetoothLeAudioCodecConfig;
43 import android.bluetooth.BluetoothLeAudioCodecStatus;
44 import android.bluetooth.BluetoothProfile;
45 import android.content.Context;
46 import android.content.Intent;
47 import android.media.AudioDeviceAttributes;
48 import android.media.AudioManager;
49 import android.media.AudioManager.AudioDeviceCategory;
50 import android.media.AudioSystem;
51 import android.media.BluetoothProfileConnectionInfo;
52 import android.os.Binder;
53 import android.os.Bundle;
54 import android.os.UserHandle;
55 import android.provider.Settings;
56 import android.text.TextUtils;
57 import android.util.Log;
58 import android.util.Pair;
59 
60 import com.android.internal.annotations.GuardedBy;
61 import com.android.server.utils.EventLogger;
62 
63 import java.io.PrintWriter;
64 import java.util.ArrayList;
65 import java.util.HashMap;
66 import java.util.List;
67 import java.util.Map;
68 import java.util.Objects;
69 
70 /**
71  * @hide
72  * Class to encapsulate all communication with Bluetooth services
73  */
74 public class BtHelper {
75 
76     private static final String TAG = "AS.BtHelper";
77 
78     private final @NonNull AudioDeviceBroker mDeviceBroker;
79     private final @NonNull Context mContext;
80 
BtHelper(@onNull AudioDeviceBroker broker, Context context)81     BtHelper(@NonNull AudioDeviceBroker broker, Context context) {
82         mDeviceBroker = broker;
83         mContext = context;
84     }
85 
86     // BluetoothHeadset API to control SCO connection
87     @GuardedBy("BtHelper.this")
88     private @Nullable BluetoothHeadset mBluetoothHeadset;
89 
90     // Bluetooth headset device
91     @GuardedBy("mDeviceBroker.mDeviceStateLock")
92     private @Nullable BluetoothDevice mBluetoothHeadsetDevice;
93 
94     @GuardedBy("mDeviceBroker.mDeviceStateLock")
95     private final Map<BluetoothDevice, AudioDeviceAttributes> mResolvedScoAudioDevices =
96             new HashMap<>();
97 
98     @GuardedBy("BtHelper.this")
99     private @Nullable BluetoothHearingAid mHearingAid = null;
100 
101     @GuardedBy("BtHelper.this")
102     private @Nullable BluetoothLeAudio mLeAudio = null;
103 
104     @GuardedBy("BtHelper.this")
105     private @Nullable BluetoothLeAudioCodecConfig mLeAudioCodecConfig;
106 
107     // Reference to BluetoothA2dp to query for AbsoluteVolume.
108     @GuardedBy("BtHelper.this")
109     private @Nullable BluetoothA2dp mA2dp = null;
110 
111     @GuardedBy("BtHelper.this")
112     private @Nullable BluetoothCodecConfig mA2dpCodecConfig;
113 
114     @GuardedBy("BtHelper.this")
115     private @AudioSystem.AudioFormatNativeEnumForBtCodec
116             int mLeAudioBroadcastCodec = AudioSystem.AUDIO_FORMAT_DEFAULT;
117 
118     // If absolute volume is supported in AVRCP device
119     @GuardedBy("mDeviceBroker.mDeviceStateLock")
120     private boolean mAvrcpAbsVolSupported = false;
121 
122     // Current connection state indicated by bluetooth headset
123     @GuardedBy("mDeviceBroker.mDeviceStateLock")
124     private int mScoConnectionState;
125 
126     // Indicate if SCO audio connection is currently active and if the initiator is
127     // audio service (internal) or bluetooth headset (external)
128     @GuardedBy("mDeviceBroker.mDeviceStateLock")
129     private int mScoAudioState;
130 
131     // Indicates the mode used for SCO audio connection. The mode is virtual call if the request
132     // originated from an app targeting an API version before JB MR2 and raw audio after that.
133     @GuardedBy("mDeviceBroker.mDeviceStateLock")
134     private int mScoAudioMode;
135 
136     // SCO audio state is not active
137     private static final int SCO_STATE_INACTIVE = 0;
138     // SCO audio activation request waiting for headset service to connect
139     private static final int SCO_STATE_ACTIVATE_REQ = 1;
140     // SCO audio state is active due to an action in BT handsfree (either voice recognition or
141     // in call audio)
142     private static final int SCO_STATE_ACTIVE_EXTERNAL = 2;
143     // SCO audio state is active or starting due to a request from AudioManager API
144     private static final int SCO_STATE_ACTIVE_INTERNAL = 3;
145     // SCO audio deactivation request waiting for headset service to connect
146     private static final int SCO_STATE_DEACTIVATE_REQ = 4;
147     // SCO audio deactivation in progress, waiting for Bluetooth audio intent
148     private static final int SCO_STATE_DEACTIVATING = 5;
149 
150     // SCO audio mode is undefined
151     /*package*/  static final int SCO_MODE_UNDEFINED = -1;
152     // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall())
153     /*package*/  static final int SCO_MODE_VIRTUAL_CALL = 0;
154     // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition())
155     private static final int SCO_MODE_VR = 2;
156     // max valid SCO audio mode values
157     private static final int SCO_MODE_MAX = 2;
158 
159     private static final int BT_HEARING_AID_GAIN_MIN = -128;
160     private static final int BT_LE_AUDIO_MAX_VOL = 255;
161 
162     // BtDevice constants currently rolling out under flag protection. Use own
163     // constants instead to avoid mainline dependency from flag library import
164     // TODO(b/335936458): remove once the BtDevice flag is rolled out
165     private static final String DEVICE_TYPE_SPEAKER = "Speaker";
166     private static final String DEVICE_TYPE_HEADSET = "Headset";
167     private static final String DEVICE_TYPE_CARKIT = "Carkit";
168     private static final String DEVICE_TYPE_HEARING_AID = "HearingAid";
169 
170     /**
171      * Returns a string representation of the scoAudioMode.
172      */
scoAudioModeToString(int scoAudioMode)173     public static String scoAudioModeToString(int scoAudioMode) {
174         switch (scoAudioMode) {
175             case SCO_MODE_UNDEFINED:
176                 return "SCO_MODE_UNDEFINED";
177             case SCO_MODE_VIRTUAL_CALL:
178                 return "SCO_MODE_VIRTUAL_CALL";
179             case SCO_MODE_VR:
180                 return "SCO_MODE_VR";
181             default:
182                 return "SCO_MODE_(" + scoAudioMode + ")";
183         }
184     }
185 
186     /**
187      * Returns a string representation of the scoAudioState.
188      */
scoAudioStateToString(int scoAudioState)189     public static String scoAudioStateToString(int scoAudioState) {
190         switch (scoAudioState) {
191             case SCO_STATE_INACTIVE:
192                 return "SCO_STATE_INACTIVE";
193             case SCO_STATE_ACTIVATE_REQ:
194                 return "SCO_STATE_ACTIVATE_REQ";
195             case SCO_STATE_ACTIVE_EXTERNAL:
196                 return "SCO_STATE_ACTIVE_EXTERNAL";
197             case SCO_STATE_ACTIVE_INTERNAL:
198                 return "SCO_STATE_ACTIVE_INTERNAL";
199             case SCO_STATE_DEACTIVATING:
200                 return "SCO_STATE_DEACTIVATING";
201             default:
202                 return "SCO_STATE_(" + scoAudioState + ")";
203         }
204     }
205 
206     // A2DP device events
207     /*package*/ static final int EVENT_DEVICE_CONFIG_CHANGE = 0;
208 
deviceEventToString(int event)209     /*package*/ static String deviceEventToString(int event) {
210         switch (event) {
211             case EVENT_DEVICE_CONFIG_CHANGE: return "DEVICE_CONFIG_CHANGE";
212             default:
213                 return new String("invalid event:" + event);
214         }
215     }
216 
getName(@onNull BluetoothDevice device)217     /*package*/ @NonNull static String getName(@NonNull BluetoothDevice device) {
218         final String deviceName = device.getName();
219         if (deviceName == null) {
220             return "";
221         }
222         return deviceName;
223     }
224 
225     //----------------------------------------------------------------------
226     // Interface for AudioDeviceBroker
227 
228     @GuardedBy("mDeviceBroker.mDeviceStateLock")
onSystemReady()229     /*package*/ synchronized void onSystemReady() {
230         mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR;
231         resetBluetoothSco();
232         getBluetoothHeadset();
233 
234         //FIXME: this is to maintain compatibility with deprecated intent
235         // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
236         Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
237         newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE,
238                 AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
239         sendStickyBroadcastToAll(newIntent);
240 
241         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
242         if (adapter != null) {
243             adapter.getProfileProxy(mDeviceBroker.getContext(),
244                     mBluetoothProfileServiceListener, BluetoothProfile.A2DP);
245             adapter.getProfileProxy(mDeviceBroker.getContext(),
246                     mBluetoothProfileServiceListener, BluetoothProfile.A2DP_SINK);
247             adapter.getProfileProxy(mDeviceBroker.getContext(),
248                     mBluetoothProfileServiceListener, BluetoothProfile.HEARING_AID);
249             adapter.getProfileProxy(mDeviceBroker.getContext(),
250                     mBluetoothProfileServiceListener, BluetoothProfile.LE_AUDIO);
251             adapter.getProfileProxy(mDeviceBroker.getContext(),
252                     mBluetoothProfileServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST);
253         }
254     }
255 
256     @GuardedBy("mDeviceBroker.mDeviceStateLock")
setAvrcpAbsoluteVolumeSupported(boolean supported)257     /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
258         mAvrcpAbsVolSupported = supported;
259         Log.i(TAG, "setAvrcpAbsoluteVolumeSupported supported=" + supported);
260     }
261 
262     @GuardedBy("mDeviceBroker.mDeviceStateLock")
setAvrcpAbsoluteVolumeIndex(int index)263     /*package*/ synchronized void setAvrcpAbsoluteVolumeIndex(int index) {
264         if (mA2dp == null) {
265             if (AudioService.DEBUG_VOL) {
266                 AudioService.sVolumeLogger.enqueue(new EventLogger.StringEvent(
267                         "setAvrcpAbsoluteVolumeIndex: bailing due to null mA2dp").printLog(TAG));
268             }
269             return;
270         }
271         if (!mAvrcpAbsVolSupported) {
272             AudioService.sVolumeLogger.enqueue(new EventLogger.StringEvent(
273                     "setAvrcpAbsoluteVolumeIndex: abs vol not supported ").printLog(TAG));
274             return;
275         }
276         if (AudioService.DEBUG_VOL) {
277             Log.i(TAG, "setAvrcpAbsoluteVolumeIndex index=" + index);
278         }
279         AudioService.sVolumeLogger.enqueue(new AudioServiceEvents.VolumeEvent(
280                 AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index));
281         try {
282             mA2dp.setAvrcpAbsoluteVolume(index);
283         } catch (Exception e) {
284             Log.e(TAG, "Exception while changing abs volume", e);
285         }
286     }
287 
getCodec( @onNull BluetoothDevice device, @AudioService.BtProfile int profile)288     private synchronized Pair<Integer, Boolean> getCodec(
289             @NonNull BluetoothDevice device, @AudioService.BtProfile int profile) {
290 
291         switch (profile) {
292             case BluetoothProfile.A2DP: {
293                 boolean changed = mA2dpCodecConfig != null;
294                 if (mA2dp == null) {
295                     mA2dpCodecConfig = null;
296                     return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed);
297                 }
298                 BluetoothCodecStatus btCodecStatus = null;
299                 try {
300                     btCodecStatus = mA2dp.getCodecStatus(device);
301                 } catch (Exception e) {
302                     Log.e(TAG, "Exception while getting status of " + device, e);
303                 }
304                 if (btCodecStatus == null) {
305                     Log.e(TAG, "getCodec, null A2DP codec status for device: " + device);
306                     mA2dpCodecConfig = null;
307                     return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed);
308                 }
309                 final BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig();
310                 if (btCodecConfig == null) {
311                     mA2dpCodecConfig = null;
312                     return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed);
313                 }
314                 changed = !btCodecConfig.equals(mA2dpCodecConfig);
315                 mA2dpCodecConfig = btCodecConfig;
316                 return new Pair<>(AudioSystem.bluetoothA2dpCodecToAudioFormat(
317                         btCodecConfig.getCodecType()), changed);
318             }
319             case BluetoothProfile.LE_AUDIO: {
320                 boolean changed = mLeAudioCodecConfig != null;
321                 if (mLeAudio == null) {
322                     mLeAudioCodecConfig = null;
323                     return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed);
324                 }
325                 BluetoothLeAudioCodecStatus btLeCodecStatus = null;
326                 int groupId = mLeAudio.getGroupId(device);
327                 try {
328                     btLeCodecStatus = mLeAudio.getCodecStatus(groupId);
329                 } catch (Exception e) {
330                     Log.e(TAG, "Exception while getting status of " + device, e);
331                 }
332                 if (btLeCodecStatus == null) {
333                     Log.e(TAG, "getCodec, null LE codec status for device: " + device);
334                     mLeAudioCodecConfig = null;
335                     return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed);
336                 }
337                 BluetoothLeAudioCodecConfig btLeCodecConfig =
338                         btLeCodecStatus.getOutputCodecConfig();
339                 if (btLeCodecConfig == null) {
340                     mLeAudioCodecConfig = null;
341                     return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed);
342                 }
343                 changed = !btLeCodecConfig.equals(mLeAudioCodecConfig);
344                 mLeAudioCodecConfig = btLeCodecConfig;
345                 return new Pair<>(AudioSystem.bluetoothLeCodecToAudioFormat(
346                         btLeCodecConfig.getCodecType()), changed);
347             }
348             case BluetoothProfile.LE_AUDIO_BROADCAST: {
349                 // We assume LC3 for LE Audio broadcast codec as there is no API to get the codec
350                 // config on LE Broadcast profile proxy.
351                 boolean changed = mLeAudioBroadcastCodec != AudioSystem.AUDIO_FORMAT_LC3;
352                 mLeAudioBroadcastCodec = AudioSystem.AUDIO_FORMAT_LC3;
353                 return new Pair<>(mLeAudioBroadcastCodec, changed);
354             }
355             default:
356                 return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, false);
357         }
358     }
359 
360     /*package*/ synchronized Pair<Integer, Boolean>
getCodecWithFallback(@onNull BluetoothDevice device, @AudioService.BtProfile int profile, boolean isLeOutput, @NonNull String source)361                     getCodecWithFallback(@NonNull BluetoothDevice device,
362                                          @AudioService.BtProfile int profile,
363                                          boolean isLeOutput, @NonNull String source) {
364         // For profiles other than A2DP and LE Audio output, the audio codec format must be
365         // AUDIO_FORMAT_DEFAULT as native audio policy manager expects a specific audio format
366         // only if audio HW module selection based on format is supported for the device type.
367         if (!(profile == BluetoothProfile.A2DP
368                 || (isLeOutput && ((profile == BluetoothProfile.LE_AUDIO)
369                         || (profile == BluetoothProfile.LE_AUDIO_BROADCAST))))) {
370             return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, false);
371         }
372         Pair<Integer, Boolean> codecAndChanged =
373                 getCodec(device, profile);
374         if (codecAndChanged.first == AudioSystem.AUDIO_FORMAT_DEFAULT) {
375             AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
376                     "getCodec DEFAULT from " + source + " fallback to "
377                             + (profile == BluetoothProfile.A2DP ? "SBC" : "LC3")));
378             return new Pair<>(profile == BluetoothProfile.A2DP
379                     ? AudioSystem.AUDIO_FORMAT_SBC : AudioSystem.AUDIO_FORMAT_LC3, true);
380         }
381 
382         return codecAndChanged;
383     }
384 
385     @GuardedBy("mDeviceBroker.mDeviceStateLock")
onReceiveBtEvent(Intent intent)386     /*package*/ synchronized void onReceiveBtEvent(Intent intent) {
387         final String action = intent.getAction();
388 
389         if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
390             BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE,
391                     android.bluetooth.BluetoothDevice.class);
392             if (btDevice != null && !isProfilePoxyConnected(BluetoothProfile.HEADSET)) {
393                 AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
394                         "onReceiveBtEvent ACTION_ACTIVE_DEVICE_CHANGED "
395                                 + "received with null profile proxy for device: "
396                                 + btDevice)).printLog(TAG));
397                 return;
398 
399             }
400             boolean deviceSwitch = optimizeBtDeviceSwitch()
401                     && btDevice != null && mBluetoothHeadsetDevice != null;
402             onSetBtScoActiveDevice(btDevice, deviceSwitch);
403         } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
404             int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
405             onScoAudioStateChanged(btState);
406         }
407     }
408 
409     /**
410      * Exclusively called from AudioDeviceBroker (with mDeviceStateLock held)
411      * when handling MSG_L_RECEIVED_BT_EVENT in {@link #onReceiveBtEvent(Intent)}
412      * as part of the serialization of the communication route selection
413      */
414     @GuardedBy("mDeviceBroker.mDeviceStateLock")
onScoAudioStateChanged(int state)415     private synchronized void onScoAudioStateChanged(int state) {
416         boolean broadcast = false;
417         int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
418         Log.i(TAG, "onScoAudioStateChanged  state: " + state
419                 + ", mScoAudioState: " + mScoAudioState);
420         switch (state) {
421             case BluetoothHeadset.STATE_AUDIO_CONNECTED:
422                 scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
423                 if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
424                         && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
425                     mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
426                 } else if (mDeviceBroker.isBluetoothScoRequested()) {
427                     // broadcast intent if the connection was initated by AudioService
428                     broadcast = true;
429                 }
430                 if (!mDeviceBroker.isScoManagedByAudio()) {
431                     mDeviceBroker.setBluetoothScoOn(
432                             true, "BtHelper.onScoAudioStateChanged, state: " + state);
433                 }
434                 break;
435             case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
436                 if (!mDeviceBroker.isScoManagedByAudio()) {
437                     mDeviceBroker.setBluetoothScoOn(
438                             false, "BtHelper.onScoAudioStateChanged, state: " + state);
439                 }
440                 scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
441                 // There are two cases where we want to immediately reconnect audio:
442                 // 1) If a new start request was received while disconnecting: this was
443                 // notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ.
444                 // 2) If audio was connected then disconnected via Bluetooth APIs and
445                 // we still have pending activation requests by apps: this is indicated by
446                 // state SCO_STATE_ACTIVE_EXTERNAL and BT SCO is requested.
447                 if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
448                     if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
449                             && connectBluetoothScoAudioHelper(mBluetoothHeadset,
450                             mBluetoothHeadsetDevice, mScoAudioMode)) {
451                         mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
452                         scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTING;
453                         broadcast = true;
454                         break;
455                     }
456                 }
457                 if (mScoAudioState != SCO_STATE_ACTIVE_EXTERNAL) {
458                     broadcast = true;
459                 }
460                 mScoAudioState = SCO_STATE_INACTIVE;
461                 break;
462             case BluetoothHeadset.STATE_AUDIO_CONNECTING:
463                 if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
464                         && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
465                     mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
466                 }
467                 break;
468             default:
469                 break;
470         }
471         if (broadcast) {
472             Log.i(TAG, "onScoAudioStateChanged  broadcasting state: " + scoAudioState);
473             broadcastScoConnectionState(scoAudioState);
474             //FIXME: this is to maintain compatibility with deprecated intent
475             // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
476             Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
477             newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState);
478             sendStickyBroadcastToAll(newIntent);
479         }
480     }
481     /**
482      *
483      * @return false if SCO isn't connected
484      */
485     @GuardedBy("mDeviceBroker.mDeviceStateLock")
isBluetoothScoOn()486     /*package*/ synchronized boolean isBluetoothScoOn() {
487         if (mBluetoothHeadset == null || mBluetoothHeadsetDevice == null) {
488             return false;
489         }
490         try {
491             return mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
492                     == BluetoothHeadset.STATE_AUDIO_CONNECTED;
493         } catch (Exception e) {
494             Log.e(TAG, "Exception while getting audio state of " + mBluetoothHeadsetDevice, e);
495         }
496         return false;
497     }
498 
499     @GuardedBy("mDeviceBroker.mDeviceStateLock")
isBluetoothScoRequestedInternally()500     /*package*/ boolean isBluetoothScoRequestedInternally() {
501         return mScoAudioState == SCO_STATE_ACTIVE_INTERNAL
502               || mScoAudioState == SCO_STATE_ACTIVATE_REQ;
503     }
504 
505     @GuardedBy("mDeviceBroker.mDeviceStateLock")
startBluetoothSco(int scoAudioMode, @NonNull String eventSource)506     /*package*/ synchronized boolean startBluetoothSco(int scoAudioMode,
507                 @NonNull String eventSource) {
508         AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource));
509         return requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
510     }
511 
512     @GuardedBy("mDeviceBroker.mDeviceStateLock")
stopBluetoothSco(@onNull String eventSource)513     /*package*/ synchronized boolean stopBluetoothSco(@NonNull String eventSource) {
514         AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource));
515         return requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, SCO_MODE_VIRTUAL_CALL);
516     }
517 
setLeAudioVolume(int index, int maxIndex, int streamType)518     /*package*/ synchronized void setLeAudioVolume(int index, int maxIndex, int streamType) {
519         if (mLeAudio == null) {
520             if (AudioService.DEBUG_VOL) {
521                 Log.i(TAG, "setLeAudioVolume: null mLeAudio");
522             }
523             return;
524         }
525         /* leaudio expect volume value in range 0 to 255 */
526         int volume = (int) Math.round((double) index * BT_LE_AUDIO_MAX_VOL / maxIndex);
527 
528         if (AudioService.DEBUG_VOL) {
529             Log.i(TAG, "setLeAudioVolume: calling mLeAudio.setVolume idx="
530                     + index + " volume=" + volume);
531         }
532         AudioService.sVolumeLogger.enqueue(new AudioServiceEvents.VolumeEvent(
533                 AudioServiceEvents.VolumeEvent.VOL_SET_LE_AUDIO_VOL, streamType, index,
534                 maxIndex, /*caller=*/null));
535         try {
536             mLeAudio.setVolume(volume);
537         } catch (Exception e) {
538             Log.e(TAG, "Exception while setting LE volume", e);
539         }
540     }
541 
setHearingAidVolume(int index, int streamType, boolean isHeadAidConnected)542     /*package*/ synchronized void setHearingAidVolume(int index, int streamType,
543             boolean isHeadAidConnected) {
544         if (mHearingAid == null) {
545             if (AudioService.DEBUG_VOL) {
546                 Log.i(TAG, "setHearingAidVolume: null mHearingAid");
547             }
548             return;
549         }
550         //hearing aid expect volume value in range -128dB to 0dB
551         int gainDB = (int) AudioSystem.getStreamVolumeDB(streamType, index / 10,
552                 AudioSystem.DEVICE_OUT_HEARING_AID);
553         if (gainDB < BT_HEARING_AID_GAIN_MIN) {
554             gainDB = BT_HEARING_AID_GAIN_MIN;
555         }
556         if (AudioService.DEBUG_VOL) {
557             Log.i(TAG, "setHearingAidVolume: calling mHearingAid.setVolume idx="
558                     + index + " gain=" + gainDB);
559         }
560         // do not log when hearing aid is not connected to avoid confusion when reading dumpsys
561         if (isHeadAidConnected) {
562             AudioService.sVolumeLogger.enqueue(new AudioServiceEvents.VolumeEvent(
563                     AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB));
564         }
565         try {
566             mHearingAid.setVolume(gainDB);
567         } catch (Exception e) {
568             Log.i(TAG, "Exception while setting hearing aid volume", e);
569         }
570     }
571 
572     @GuardedBy("mDeviceBroker.mDeviceStateLock")
onBroadcastScoConnectionState(int state)573     /*package*/ void onBroadcastScoConnectionState(int state) {
574         if (state == mScoConnectionState) {
575             return;
576         }
577         Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
578         newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state);
579         newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE,
580                 mScoConnectionState);
581         sendStickyBroadcastToAll(newIntent);
582         mScoConnectionState = state;
583     }
584 
585     @GuardedBy("mDeviceBroker.mDeviceStateLock")
resetBluetoothSco()586     /*package*/ void resetBluetoothSco() {
587         mScoAudioState = SCO_STATE_INACTIVE;
588         broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
589         mDeviceBroker.clearA2dpSuspended(false /* internalOnly */);
590         mDeviceBroker.clearLeAudioSuspended(false /* internalOnly */);
591         mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
592     }
593 
594     @GuardedBy("mDeviceBroker.mDeviceStateLock")
onBtProfileDisconnected(int profile)595     /*package*/ synchronized void onBtProfileDisconnected(int profile) {
596         AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
597                 "BT profile " + BluetoothProfile.getProfileName(profile)
598                 + " disconnected").printLog(TAG));
599         switch (profile) {
600             case BluetoothProfile.HEADSET:
601                 mBluetoothHeadset = null;
602                 break;
603             case BluetoothProfile.A2DP:
604                 mA2dp = null;
605                 mA2dpCodecConfig = null;
606                 break;
607             case BluetoothProfile.HEARING_AID:
608                 mHearingAid = null;
609                 break;
610             case BluetoothProfile.LE_AUDIO:
611                 if (mLeAudio != null && mLeAudioCallback != null) {
612                     try {
613                         mLeAudio.unregisterCallback(mLeAudioCallback);
614                     } catch (Exception e) {
615                         Log.e(TAG, "Exception while unregistering callback for LE audio", e);
616                     }
617                 }
618                 mLeAudio = null;
619                 mLeAudioCallback = null;
620                 mLeAudioCodecConfig = null;
621                 break;
622             case BluetoothProfile.LE_AUDIO_BROADCAST:
623                 mLeAudioBroadcastCodec = AudioSystem.AUDIO_FORMAT_DEFAULT;
624                 break;
625             case BluetoothProfile.A2DP_SINK:
626                 // nothing to do in BtHelper
627                 break;
628             default:
629                 // Not a valid profile to disconnect
630                 Log.e(TAG, "onBtProfileDisconnected: Not a valid profile to disconnect "
631                         + BluetoothProfile.getProfileName(profile));
632                 break;
633         }
634     }
635 
636     // BluetoothLeAudio callback used to update the list of addresses in the same group as a
637     // connected LE Audio device
638     class MyLeAudioCallback implements BluetoothLeAudio.Callback {
639         @Override
onCodecConfigChanged(int groupId, @NonNull BluetoothLeAudioCodecStatus status)640         public void onCodecConfigChanged(int groupId,
641                                   @NonNull BluetoothLeAudioCodecStatus status) {
642             // Do nothing
643         }
644 
645         @Override
onGroupNodeAdded(@onNull BluetoothDevice device, int groupId)646         public void onGroupNodeAdded(@NonNull BluetoothDevice device, int groupId) {
647             mDeviceBroker.postUpdateLeAudioGroupAddresses(groupId);
648         }
649 
650         @Override
onGroupNodeRemoved(@onNull BluetoothDevice device, int groupId)651         public void onGroupNodeRemoved(@NonNull BluetoothDevice device, int groupId) {
652             mDeviceBroker.postUpdateLeAudioGroupAddresses(groupId);
653         }
654         @Override
onGroupStatusChanged(int groupId, int groupStatus)655         public void onGroupStatusChanged(int groupId, int groupStatus) {
656             mDeviceBroker.postUpdateLeAudioGroupAddresses(groupId);
657         }
658     }
659 
660     @GuardedBy("BtHelper.this")
661     MyLeAudioCallback mLeAudioCallback = null;
662 
663     @GuardedBy("mDeviceBroker.mDeviceStateLock")
onBtProfileConnected(int profile, BluetoothProfile proxy)664     /*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) {
665         AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
666                 "BT profile " + BluetoothProfile.getProfileName(profile) + " connected to proxy "
667                 + proxy).printLog(TAG));
668         if (proxy == null) {
669             Log.e(TAG, "onBtProfileConnected: null proxy for profile: " + profile);
670             return;
671         }
672         switch (profile) {
673             case BluetoothProfile.HEADSET:
674                 onHeadsetProfileConnected((BluetoothHeadset) proxy);
675                 return;
676             case BluetoothProfile.A2DP:
677                 if (((BluetoothA2dp) proxy).equals(mA2dp)) {
678                     return;
679                 }
680                 mA2dp = (BluetoothA2dp) proxy;
681                 break;
682             case BluetoothProfile.HEARING_AID:
683                 if (((BluetoothHearingAid) proxy).equals(mHearingAid)) {
684                     return;
685                 }
686                 mHearingAid = (BluetoothHearingAid) proxy;
687                 break;
688             case BluetoothProfile.LE_AUDIO:
689                 if (((BluetoothLeAudio) proxy).equals(mLeAudio)) {
690                     return;
691                 }
692                 if (mLeAudio != null && mLeAudioCallback != null) {
693                     try {
694                         mLeAudio.unregisterCallback(mLeAudioCallback);
695                     } catch (Exception e) {
696                         Log.e(TAG, "Exception while unregistering callback for LE audio", e);
697                     }
698                 }
699                 mLeAudio = (BluetoothLeAudio) proxy;
700                 mLeAudioCallback = new MyLeAudioCallback();
701                 try{
702                     mLeAudio.registerCallback(
703                                 mContext.getMainExecutor(), mLeAudioCallback);
704                 } catch (Exception e) {
705                     mLeAudioCallback = null;
706                     Log.e(TAG, "Exception while registering callback for LE audio", e);
707                 }
708                 break;
709             case BluetoothProfile.A2DP_SINK:
710             case BluetoothProfile.LE_AUDIO_BROADCAST:
711                 // nothing to do in BtHelper
712                 return;
713             default:
714                 // Not a valid profile to connect
715                 Log.e(TAG, "onBtProfileConnected: Not a valid profile to connect "
716                         + BluetoothProfile.getProfileName(profile));
717                 return;
718         }
719 
720         // this part is only for A2DP, LE Audio unicast and Hearing aid
721         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
722         if (adapter == null) {
723             Log.e(TAG, "onBtProfileConnected: Null BluetoothAdapter when connecting profile: "
724                     + BluetoothProfile.getProfileName(profile));
725             return;
726         }
727         List<BluetoothDevice> activeDevices = adapter.getActiveDevices(profile);
728         if (activeDevices.isEmpty() || activeDevices.get(0) == null) {
729             return;
730         }
731         BluetoothDevice device = activeDevices.get(0);
732         switch (profile) {
733             case BluetoothProfile.A2DP: {
734                 BluetoothProfileConnectionInfo bpci =
735                         BluetoothProfileConnectionInfo.createA2dpInfo(false, -1);
736                 postBluetoothActiveDevice(device, bpci);
737             } break;
738             case BluetoothProfile.HEARING_AID: {
739                 BluetoothProfileConnectionInfo bpci =
740                         BluetoothProfileConnectionInfo.createHearingAidInfo(false);
741                 postBluetoothActiveDevice(device, bpci);
742             } break;
743             case BluetoothProfile.LE_AUDIO: {
744                 int groupId = mLeAudio.getGroupId(device);
745                 BluetoothLeAudioCodecStatus btLeCodecStatus = null;
746                 try {
747                     btLeCodecStatus = mLeAudio.getCodecStatus(groupId);
748                 } catch (Exception e) {
749                     Log.e(TAG, "Exception while getting status of " + device, e);
750                 }
751                 if (btLeCodecStatus == null) {
752                     Log.i(TAG, "onBtProfileConnected null LE codec status for groupId: "
753                             + groupId + ", device: " + device);
754                     break;
755                 }
756                 List<BluetoothLeAudioCodecConfig> outputCodecConfigs =
757                         btLeCodecStatus.getOutputCodecSelectableCapabilities();
758                 if (!outputCodecConfigs.isEmpty()) {
759                     BluetoothProfileConnectionInfo bpci =
760                             BluetoothProfileConnectionInfo.createLeAudioInfo(
761                                     false /*suppressNoisyIntent*/, true /*isLeOutput*/);
762                     postBluetoothActiveDevice(device, bpci);
763                 }
764                 List<BluetoothLeAudioCodecConfig> inputCodecConfigs =
765                         btLeCodecStatus.getInputCodecSelectableCapabilities();
766                 if (!inputCodecConfigs.isEmpty()) {
767                     BluetoothProfileConnectionInfo bpci =
768                             BluetoothProfileConnectionInfo.createLeAudioInfo(
769                                     false /*suppressNoisyIntent*/, false /*isLeOutput*/);
770                     postBluetoothActiveDevice(device, bpci);
771                 }
772             } break;
773             default:
774                 // Not a valid profile to connect
775                 Log.wtf(TAG, "Invalid profile! onBtProfileConnected");
776                 break;
777         }
778     }
779 
postBluetoothActiveDevice( BluetoothDevice device, BluetoothProfileConnectionInfo bpci)780     private void postBluetoothActiveDevice(
781             BluetoothDevice device, BluetoothProfileConnectionInfo bpci) {
782         AudioDeviceBroker.BtDeviceChangedData data = new AudioDeviceBroker.BtDeviceChangedData(
783                 device, null, bpci, "mBluetoothProfileServiceListener");
784         AudioDeviceBroker.BtDeviceInfo info = mDeviceBroker.createBtDeviceInfo(
785                 data, device, BluetoothProfile.STATE_CONNECTED);
786         mDeviceBroker.postBluetoothActiveDevice(info, 0 /* delay */);
787     }
788 
isProfilePoxyConnected(int profile)789     /*package*/ synchronized boolean isProfilePoxyConnected(int profile) {
790         switch (profile) {
791             case BluetoothProfile.HEADSET:
792                 return mBluetoothHeadset != null;
793             case BluetoothProfile.A2DP:
794                 return mA2dp != null;
795             case BluetoothProfile.HEARING_AID:
796                 return mHearingAid != null;
797             case BluetoothProfile.LE_AUDIO:
798                 return mLeAudio != null;
799             case BluetoothProfile.A2DP_SINK:
800             case BluetoothProfile.LE_AUDIO_BROADCAST:
801             default:
802                 // return true for profiles that are not managed by the BtHelper because
803                 // the fact that the profile proxy is not connected does not affect
804                 // the device connection handling.
805                 return true;
806         }
807     }
808 
809     @GuardedBy("mDeviceBroker.mDeviceStateLock")
onHeadsetProfileConnected(@onNull BluetoothHeadset headset)810     private synchronized void onHeadsetProfileConnected(@NonNull BluetoothHeadset headset) {
811         // Discard timeout message
812         mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService();
813         mBluetoothHeadset = headset;
814         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
815         if (adapter != null) {
816             List<BluetoothDevice> activeDevices =
817                     adapter.getActiveDevices(BluetoothProfile.HEADSET);
818             for (BluetoothDevice device : activeDevices) {
819                 if (device == null) {
820                     continue;
821                 }
822                 onSetBtScoActiveDevice(device, false /*deviceSwitch*/);
823             }
824         } else {
825             Log.e(TAG, "onHeadsetProfileConnected: Null BluetoothAdapter");
826         }
827 
828         // Refresh SCO audio state
829         checkScoAudioState();
830         if (mScoAudioState != SCO_STATE_ACTIVATE_REQ
831                 && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
832             return;
833         }
834         boolean status = false;
835         if (mBluetoothHeadsetDevice != null) {
836             switch (mScoAudioState) {
837                 case SCO_STATE_ACTIVATE_REQ:
838                     status = connectBluetoothScoAudioHelper(
839                             mBluetoothHeadset,
840                             mBluetoothHeadsetDevice, mScoAudioMode);
841                     if (status) {
842                         mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
843                     }
844                     break;
845                 case SCO_STATE_DEACTIVATE_REQ:
846                     status = disconnectBluetoothScoAudioHelper(
847                             mBluetoothHeadset,
848                             mBluetoothHeadsetDevice, mScoAudioMode);
849                     if (status) {
850                         mScoAudioState = SCO_STATE_DEACTIVATING;
851                     }
852                     break;
853             }
854         }
855         if (!status) {
856             mScoAudioState = SCO_STATE_INACTIVE;
857             broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
858         }
859     }
860 
861     //----------------------------------------------------------------------
broadcastScoConnectionState(int state)862     private void broadcastScoConnectionState(int state) {
863         mDeviceBroker.postBroadcastScoConnectionState(state);
864     }
865 
866     @GuardedBy("mDeviceBroker.mDeviceStateLock")
getHeadsetAudioDevice()867     @Nullable AudioDeviceAttributes getHeadsetAudioDevice() {
868         if (mBluetoothHeadsetDevice == null) {
869             return null;
870         }
871         return getHeadsetAudioDevice(mBluetoothHeadsetDevice);
872     }
873 
874     @GuardedBy("mDeviceBroker.mDeviceStateLock")
getHeadsetAudioDevice(BluetoothDevice btDevice)875     private @NonNull AudioDeviceAttributes getHeadsetAudioDevice(BluetoothDevice btDevice) {
876         AudioDeviceAttributes deviceAttr = mResolvedScoAudioDevices.get(btDevice);
877         if (deviceAttr != null) {
878             // Returns the cached device attributes so that it is consistent as the previous one.
879             return deviceAttr;
880         }
881         return btHeadsetDeviceToAudioDevice(btDevice);
882     }
883 
btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice)884     private static AudioDeviceAttributes btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice) {
885         if (btDevice == null) {
886             return new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, "");
887         }
888         String address = btDevice.getAddress();
889         String name = getName(btDevice);
890         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
891             address = "";
892         }
893         BluetoothClass btClass = btDevice.getBluetoothClass();
894         int nativeType = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO;
895         if (btClass != null) {
896             switch (btClass.getDeviceClass()) {
897                 case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
898                 case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
899                     nativeType = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
900                     break;
901                 case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
902                     nativeType = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
903                     break;
904             }
905         }
906         if (AudioService.DEBUG_DEVICES) {
907             Log.i(TAG, "btHeadsetDeviceToAudioDevice btDevice: " + btDevice
908                     + " btClass: " + (btClass == null ? "Unknown" : btClass)
909                     + " nativeType: " + nativeType + " address: " + address);
910         }
911         return new AudioDeviceAttributes(nativeType, address, name);
912     }
913 
914     @GuardedBy("mDeviceBroker.mDeviceStateLock")
handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive, boolean deviceSwitch)915     private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive,
916             boolean deviceSwitch) {
917         if (btDevice == null) {
918             return true;
919         }
920         boolean result = false;
921         AudioDeviceAttributes audioDevice = null; // Only used if isActive is true
922         String address = btDevice.getAddress();
923         String name = getName(btDevice);
924         // Handle output device
925         if (isActive) {
926             audioDevice = btHeadsetDeviceToAudioDevice(btDevice);
927             result = mDeviceBroker.handleDeviceConnection(
928                     audioDevice, true /*connect*/, btDevice, false /*deviceSwitch*/);
929         } else {
930             AudioDeviceAttributes ada = mResolvedScoAudioDevices.get(btDevice);
931             if (ada != null) {
932                 result = mDeviceBroker.handleDeviceConnection(
933                     ada, false /*connect*/, btDevice, deviceSwitch);
934             } else {
935                 // Disconnect all possible audio device types if the disconnected device type is
936                 // unknown
937                 int[] outDeviceTypes = {
938                     AudioSystem.DEVICE_OUT_BLUETOOTH_SCO,
939                     AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET,
940                     AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT
941                 };
942                 for (int outDeviceType : outDeviceTypes) {
943                     result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
944                             outDeviceType, address, name), false /*connect*/, btDevice,
945                             deviceSwitch);
946                 }
947             }
948         }
949         // Handle input device
950         int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
951         // handleDeviceConnection() && result to make sure the method get executed
952         result = mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
953                         inDevice, address, name),
954                 isActive, btDevice, deviceSwitch) && result;
955         if (result) {
956             if (isActive) {
957                 mResolvedScoAudioDevices.put(btDevice, audioDevice);
958             } else {
959                 mResolvedScoAudioDevices.remove(btDevice);
960             }
961         }
962         return result;
963     }
964 
965     // Return `(null)` if given BluetoothDevice is null. Otherwise, return the anonymized address.
getAnonymizedAddress(BluetoothDevice btDevice)966     private String getAnonymizedAddress(BluetoothDevice btDevice) {
967         return btDevice == null ? "(null)" : btDevice.getAnonymizedAddress();
968     }
969 
970     @GuardedBy("mDeviceBroker.mDeviceStateLock")
onSetBtScoActiveDevice(BluetoothDevice btDevice, boolean deviceSwitch)971     /*package */ void onSetBtScoActiveDevice(BluetoothDevice btDevice, boolean deviceSwitch) {
972         Log.i(TAG, "onSetBtScoActiveDevice: " + getAnonymizedAddress(mBluetoothHeadsetDevice)
973                 + " -> " + getAnonymizedAddress(btDevice) + ", deviceSwitch: " + deviceSwitch);
974         final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
975         if (Objects.equals(btDevice, previousActiveDevice)) {
976             return;
977         }
978         if (!handleBtScoActiveDeviceChange(previousActiveDevice, false, deviceSwitch)) {
979             Log.w(TAG, "onSetBtScoActiveDevice() failed to remove previous device "
980                     + getAnonymizedAddress(previousActiveDevice));
981         }
982         if (!handleBtScoActiveDeviceChange(btDevice, true, false /*deviceSwitch*/)) {
983             Log.e(TAG, "onSetBtScoActiveDevice() failed to add new device "
984                     + getAnonymizedAddress(btDevice));
985             // set mBluetoothHeadsetDevice to null when failing to add new device
986             btDevice = null;
987         }
988         mBluetoothHeadsetDevice = btDevice;
989         if (mBluetoothHeadsetDevice == null) {
990             resetBluetoothSco();
991         }
992     }
993 
994     // NOTE this listener is NOT called from AudioDeviceBroker event thread, only call async
995     //      methods inside listener.
996     private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
997             new BluetoothProfile.ServiceListener() {
998                 public void onServiceConnected(int profile, BluetoothProfile proxy) {
999                     switch(profile) {
1000                         case BluetoothProfile.A2DP:
1001                         case BluetoothProfile.HEADSET:
1002                         case BluetoothProfile.HEARING_AID:
1003                         case BluetoothProfile.LE_AUDIO:
1004                         case BluetoothProfile.A2DP_SINK:
1005                         case BluetoothProfile.LE_AUDIO_BROADCAST:
1006                             AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
1007                                     "BT profile service: connecting "
1008                                     + BluetoothProfile.getProfileName(profile)
1009                                     + " profile").printLog(TAG));
1010                             mDeviceBroker.postBtProfileConnected(profile, proxy);
1011                             break;
1012 
1013                         default:
1014                             break;
1015                     }
1016                 }
1017                 public void onServiceDisconnected(int profile) {
1018 
1019                     switch (profile) {
1020                         case BluetoothProfile.A2DP:
1021                         case BluetoothProfile.HEADSET:
1022                         case BluetoothProfile.HEARING_AID:
1023                         case BluetoothProfile.LE_AUDIO:
1024                         case BluetoothProfile.A2DP_SINK:
1025                         case BluetoothProfile.LE_AUDIO_BROADCAST:
1026                             AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
1027                                     "BT profile service: disconnecting "
1028                                         + BluetoothProfile.getProfileName(profile)
1029                                         + " profile").printLog(TAG));
1030                             mDeviceBroker.postBtProfileDisconnected(profile);
1031                             break;
1032 
1033                         default:
1034                             break;
1035                     }
1036                 }
1037             };
1038 
1039     //----------------------------------------------------------------------
1040 
1041     @GuardedBy("mDeviceBroker.mDeviceStateLock")
requestScoState(int state, int scoAudioMode)1042     private synchronized boolean requestScoState(int state, int scoAudioMode) {
1043         checkScoAudioState();
1044         if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
1045             // Make sure that the state transitions to CONNECTING even if we cannot initiate
1046             // the connection except if already connected internally
1047             if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL) {
1048                 broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
1049             }
1050             switch (mScoAudioState) {
1051                 case SCO_STATE_INACTIVE:
1052                     mScoAudioMode = scoAudioMode;
1053                     if (scoAudioMode == SCO_MODE_UNDEFINED) {
1054                         mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
1055                         if (mBluetoothHeadsetDevice != null) {
1056                             mScoAudioMode = Settings.Global.getInt(
1057                                     mDeviceBroker.getContentResolver(),
1058                                     "bluetooth_sco_channel_"
1059                                             + mBluetoothHeadsetDevice.getAddress(),
1060                                     SCO_MODE_VIRTUAL_CALL);
1061                             if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) {
1062                                 mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
1063                             }
1064                         }
1065                     }
1066                     if (mBluetoothHeadset == null) {
1067                         if (getBluetoothHeadset()) {
1068                             mScoAudioState = SCO_STATE_ACTIVATE_REQ;
1069                         } else {
1070                             Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
1071                                     + " connection, mScoAudioMode=" + mScoAudioMode);
1072                             broadcastScoConnectionState(
1073                                     AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
1074                             return false;
1075                         }
1076                         break;
1077                     }
1078                     if (mBluetoothHeadsetDevice == null) {
1079                         Log.w(TAG, "requestScoState: no active device while connecting,"
1080                                 + " mScoAudioMode=" + mScoAudioMode);
1081                         broadcastScoConnectionState(
1082                                 AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
1083                         return false;
1084                     }
1085                     if (connectBluetoothScoAudioHelper(mBluetoothHeadset,
1086                             mBluetoothHeadsetDevice, mScoAudioMode)) {
1087                         mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
1088                     } else {
1089                         Log.w(TAG, "requestScoState: connect to "
1090                                 + getAnonymizedAddress(mBluetoothHeadsetDevice)
1091                                 + " failed, mScoAudioMode=" + mScoAudioMode);
1092                         broadcastScoConnectionState(
1093                                 AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
1094                         return false;
1095                     }
1096                     break;
1097                 case SCO_STATE_DEACTIVATING:
1098                     mScoAudioState = SCO_STATE_ACTIVATE_REQ;
1099                     break;
1100                 case SCO_STATE_DEACTIVATE_REQ:
1101                     mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
1102                     broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
1103                     break;
1104                 case SCO_STATE_ACTIVE_INTERNAL:
1105                     // Already in ACTIVE mode, simply return
1106                     break;
1107                 case SCO_STATE_ACTIVE_EXTERNAL:
1108                     /* Confirm SCO Audio connection to requesting app as it is already connected
1109                      * externally (i.e. through SCO APIs by Telecom service).
1110                      * Once SCO Audio is disconnected by the external owner, we will reconnect it
1111                      * automatically on behalf of the requesting app and the state will move to
1112                      * SCO_STATE_ACTIVE_INTERNAL.
1113                      */
1114                     broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
1115                     break;
1116                 default:
1117                     Log.w(TAG, "requestScoState: failed to connect in state "
1118                             + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
1119                     broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
1120                     return false;
1121             }
1122         } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
1123             switch (mScoAudioState) {
1124                 case SCO_STATE_ACTIVE_INTERNAL:
1125                     if (mBluetoothHeadset == null) {
1126                         if (getBluetoothHeadset()) {
1127                             mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
1128                         } else {
1129                             Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
1130                                     + " disconnection, mScoAudioMode=" + mScoAudioMode);
1131                             mScoAudioState = SCO_STATE_INACTIVE;
1132                             broadcastScoConnectionState(
1133                                     AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
1134                             return false;
1135                         }
1136                         break;
1137                     }
1138                     if (mBluetoothHeadsetDevice == null) {
1139                         mScoAudioState = SCO_STATE_INACTIVE;
1140                         broadcastScoConnectionState(
1141                                 AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
1142                         break;
1143                     }
1144                     if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
1145                             mBluetoothHeadsetDevice, mScoAudioMode)) {
1146                         mScoAudioState = SCO_STATE_DEACTIVATING;
1147                     } else {
1148                         mScoAudioState = SCO_STATE_INACTIVE;
1149                         broadcastScoConnectionState(
1150                                 AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
1151                     }
1152                     break;
1153                 case SCO_STATE_ACTIVATE_REQ:
1154                     mScoAudioState = SCO_STATE_INACTIVE;
1155                     broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
1156                     break;
1157                 default:
1158                     Log.w(TAG, "requestScoState: failed to disconnect in state "
1159                             + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
1160                     broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
1161                     return false;
1162             }
1163         }
1164         return true;
1165     }
1166 
1167     //-----------------------------------------------------
1168     // Utilities
1169 
1170     // suppress warning due to generic Intent passed as param
1171     @SuppressWarnings("AndroidFrameworkRequiresPermission")
sendStickyBroadcastToAll(Intent intent)1172     private void sendStickyBroadcastToAll(Intent intent) {
1173         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
1174         final long ident = Binder.clearCallingIdentity();
1175         try {
1176             mDeviceBroker.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
1177         } finally {
1178             Binder.restoreCallingIdentity(ident);
1179         }
1180     }
1181 
disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, BluetoothDevice device, int scoAudioMode)1182     private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
1183             BluetoothDevice device, int scoAudioMode) {
1184         switch (scoAudioMode) {
1185             case SCO_MODE_VIRTUAL_CALL:
1186                 return bluetoothHeadset.stopScoUsingVirtualVoiceCall();
1187             case SCO_MODE_VR:
1188                 return bluetoothHeadset.stopVoiceRecognition(device);
1189             default:
1190                 return false;
1191         }
1192     }
1193 
connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, BluetoothDevice device, int scoAudioMode)1194     private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
1195             BluetoothDevice device, int scoAudioMode) {
1196         switch (scoAudioMode) {
1197             case SCO_MODE_VIRTUAL_CALL:
1198                 return bluetoothHeadset.startScoUsingVirtualVoiceCall();
1199             case SCO_MODE_VR:
1200                 return bluetoothHeadset.startVoiceRecognition(device);
1201             default:
1202                 return false;
1203         }
1204     }
1205 
1206     @GuardedBy("mDeviceBroker.mDeviceStateLock")
checkScoAudioState()1207     private synchronized void checkScoAudioState() {
1208         try {
1209             if (mBluetoothHeadset != null
1210                     && mBluetoothHeadsetDevice != null
1211                     && mScoAudioState == SCO_STATE_INACTIVE
1212                     && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
1213                         != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
1214                 mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
1215             }
1216         } catch (Exception e) {
1217             Log.e(TAG, "Exception while getting audio state of " + mBluetoothHeadsetDevice, e);
1218         }
1219     }
1220 
getBluetoothHeadset()1221     private boolean getBluetoothHeadset() {
1222         boolean result = false;
1223         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
1224         if (adapter != null) {
1225             result = adapter.getProfileProxy(mDeviceBroker.getContext(),
1226                     mBluetoothProfileServiceListener, BluetoothProfile.HEADSET);
1227         }
1228         // If we could not get a bluetooth headset proxy, send a failure message
1229         // without delay to reset the SCO audio state and clear SCO clients.
1230         // If we could get a proxy, send a delayed failure message that will reset our state
1231         // in case we don't receive onServiceConnected().
1232         mDeviceBroker.handleFailureToConnectToBtHeadsetService(
1233                 result ? AudioDeviceBroker.BT_HEADSET_CNCT_TIMEOUT_MS : 0);
1234         return result;
1235     }
1236 
getLeAudioDeviceGroupId(BluetoothDevice device)1237     /*package*/ synchronized int getLeAudioDeviceGroupId(BluetoothDevice device) {
1238         if (mLeAudio == null || device == null) {
1239             return BluetoothLeAudio.GROUP_ID_INVALID;
1240         }
1241         return mLeAudio.getGroupId(device);
1242     }
1243 
1244     /**
1245      * Returns all addresses and identity addresses for LE Audio devices a group.
1246      * @param groupId The ID of the group from which to get addresses.
1247      * @return A List of Pair(String main_address, String identity_address). Note that the
1248      * addresses returned by BluetoothDevice can be null.
1249      */
getLeAudioGroupAddresses(int groupId)1250     /*package*/ synchronized List<Pair<String, String>> getLeAudioGroupAddresses(int groupId) {
1251         List<Pair<String, String>> addresses = new ArrayList<>();
1252         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
1253         if (adapter == null || mLeAudio == null) {
1254             return addresses;
1255         }
1256         List<BluetoothDevice> activeDevices = adapter.getActiveDevices(BluetoothProfile.LE_AUDIO);
1257         for (BluetoothDevice device : activeDevices) {
1258             if (device != null && mLeAudio.getGroupId(device) == groupId) {
1259                 addresses.add(new Pair(device.getAddress(), device.getIdentityAddress()));
1260             }
1261         }
1262         return addresses;
1263     }
1264 
1265     /**
1266      * Returns the String equivalent of the btCodecType.
1267      *
1268      * This uses an "ENCODING_" prefix for consistency with Audio;
1269      * we could alternately use the "SOURCE_CODEC_TYPE_" prefix from Bluetooth.
1270      */
bluetoothCodecToEncodingString(int btCodecType)1271     public static String bluetoothCodecToEncodingString(int btCodecType) {
1272         switch (btCodecType) {
1273             case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC:
1274                 return "ENCODING_SBC";
1275             case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC:
1276                 return "ENCODING_AAC";
1277             case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX:
1278                 return "ENCODING_APTX";
1279             case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD:
1280                 return "ENCODING_APTX_HD";
1281             case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC:
1282                 return "ENCODING_LDAC";
1283             case BluetoothCodecConfig.SOURCE_CODEC_TYPE_OPUS:
1284                 return "ENCODING_OPUS";
1285             default:
1286                 return "ENCODING_BT_CODEC_TYPE(" + btCodecType + ")";
1287         }
1288     }
1289 
getProfileFromType(int deviceType)1290     /*package */ static int getProfileFromType(int deviceType) {
1291         if (AudioSystem.isBluetoothA2dpOutDevice(deviceType)) {
1292             return BluetoothProfile.A2DP;
1293         } else if (AudioSystem.isBluetoothScoDevice(deviceType)) {
1294             return BluetoothProfile.HEADSET;
1295         } else if (AudioSystem.isBluetoothLeDevice(deviceType)) {
1296             return BluetoothProfile.LE_AUDIO;
1297         }
1298         return 0; // 0 is not a valid profile
1299     }
1300 
getTypeFromProfile(int profile, boolean isLeOutput)1301     /*package */ static int getTypeFromProfile(int profile, boolean isLeOutput) {
1302         switch (profile) {
1303             case BluetoothProfile.A2DP_SINK:
1304                 return AudioSystem.DEVICE_IN_BLUETOOTH_A2DP;
1305             case BluetoothProfile.A2DP:
1306                 return AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
1307             case BluetoothProfile.HEARING_AID:
1308                 return AudioSystem.DEVICE_OUT_HEARING_AID;
1309             case BluetoothProfile.LE_AUDIO:
1310                 if (isLeOutput) {
1311                     return AudioSystem.DEVICE_OUT_BLE_HEADSET;
1312                 } else {
1313                     return AudioSystem.DEVICE_IN_BLE_HEADSET;
1314                 }
1315             case BluetoothProfile.LE_AUDIO_BROADCAST:
1316                 return AudioSystem.DEVICE_OUT_BLE_BROADCAST;
1317             case BluetoothProfile.HEADSET:
1318                 return AudioSystem.DEVICE_OUT_BLUETOOTH_SCO;
1319             default:
1320                 throw new IllegalArgumentException("Invalid profile " + profile);
1321         }
1322     }
1323 
getPreferredAudioProfiles(String address)1324     /*package */ static Bundle getPreferredAudioProfiles(String address) {
1325         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
1326         return adapter.getPreferredAudioProfiles(adapter.getRemoteDevice(address));
1327     }
1328 
1329     @Nullable
getBluetoothDevice(String address)1330     /*package */ static BluetoothDevice getBluetoothDevice(String address) {
1331         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
1332         if (adapter == null || !BluetoothAdapter.checkBluetoothAddress(address)) {
1333             return null;
1334         }
1335 
1336         return adapter.getRemoteDevice(address);
1337     }
1338 
1339     @AudioDeviceCategory
getBtDeviceCategory(String address)1340     /*package*/ static int getBtDeviceCategory(String address) {
1341         BluetoothDevice device = BtHelper.getBluetoothDevice(address);
1342         if (device == null) {
1343             return AUDIO_DEVICE_CATEGORY_UNKNOWN;
1344         }
1345 
1346         byte[] deviceType = device.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE);
1347         if (deviceType == null) {
1348             return AUDIO_DEVICE_CATEGORY_UNKNOWN;
1349         }
1350         String deviceCategory = new String(deviceType);
1351         switch (deviceCategory) {
1352             case DEVICE_TYPE_HEARING_AID:
1353                 return AUDIO_DEVICE_CATEGORY_HEARING_AID;
1354             case DEVICE_TYPE_CARKIT:
1355                 return AUDIO_DEVICE_CATEGORY_CARKIT;
1356             case DEVICE_TYPE_HEADSET:
1357             case DEVICE_TYPE_UNTETHERED_HEADSET:
1358                 return AUDIO_DEVICE_CATEGORY_HEADPHONES;
1359             case DEVICE_TYPE_SPEAKER:
1360                 return AUDIO_DEVICE_CATEGORY_SPEAKER;
1361             case DEVICE_TYPE_WATCH:
1362                 return AUDIO_DEVICE_CATEGORY_WATCH;
1363             case DEVICE_TYPE_DEFAULT:
1364             default:
1365                 // fall through
1366         }
1367 
1368         BluetoothClass deviceClass = device.getBluetoothClass();
1369         if (deviceClass == null) {
1370             return AUDIO_DEVICE_CATEGORY_UNKNOWN;
1371         }
1372 
1373         switch (deviceClass.getDeviceClass()) {
1374             case BluetoothClass.Device.WEARABLE_WRIST_WATCH:
1375                 return AUDIO_DEVICE_CATEGORY_WATCH;
1376             case BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER:
1377             case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER:
1378             case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO:
1379                 return AUDIO_DEVICE_CATEGORY_SPEAKER;
1380             case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
1381             case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES:
1382                 return AUDIO_DEVICE_CATEGORY_HEADPHONES;
1383             case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO:
1384                 return AUDIO_DEVICE_CATEGORY_RECEIVER;
1385             default:
1386                 return AUDIO_DEVICE_CATEGORY_UNKNOWN;
1387         }
1388     }
1389 
1390     /**
1391      * Notifies Bluetooth framework that new preferred audio profiles for Bluetooth devices
1392      * have been applied.
1393      */
onNotifyPreferredAudioProfileApplied(BluetoothDevice btDevice)1394     public static void onNotifyPreferredAudioProfileApplied(BluetoothDevice btDevice) {
1395         BluetoothAdapter.getDefaultAdapter().notifyActiveDeviceChangeApplied(btDevice);
1396     }
1397 
1398     /**
1399      * Returns the string equivalent for the btDeviceClass class.
1400      */
btDeviceClassToString(int btDeviceClass)1401     public static String btDeviceClassToString(int btDeviceClass) {
1402         switch (btDeviceClass) {
1403             case BluetoothClass.Device.AUDIO_VIDEO_UNCATEGORIZED:
1404                 return "AUDIO_VIDEO_UNCATEGORIZED";
1405             case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
1406                 return "AUDIO_VIDEO_WEARABLE_HEADSET";
1407             case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
1408                 return "AUDIO_VIDEO_HANDSFREE";
1409             case 0x040C:
1410                 return "AUDIO_VIDEO_RESERVED_0x040C"; // uncommon
1411             case BluetoothClass.Device.AUDIO_VIDEO_MICROPHONE:
1412                 return "AUDIO_VIDEO_MICROPHONE";
1413             case BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER:
1414                 return "AUDIO_VIDEO_LOUDSPEAKER";
1415             case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES:
1416                 return "AUDIO_VIDEO_HEADPHONES";
1417             case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO:
1418                 return "AUDIO_VIDEO_PORTABLE_AUDIO";
1419             case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
1420                 return "AUDIO_VIDEO_CAR_AUDIO";
1421             case BluetoothClass.Device.AUDIO_VIDEO_SET_TOP_BOX:
1422                 return "AUDIO_VIDEO_SET_TOP_BOX";
1423             case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO:
1424                 return "AUDIO_VIDEO_HIFI_AUDIO";
1425             case BluetoothClass.Device.AUDIO_VIDEO_VCR:
1426                 return "AUDIO_VIDEO_VCR";
1427             case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_CAMERA:
1428                 return "AUDIO_VIDEO_VIDEO_CAMERA";
1429             case BluetoothClass.Device.AUDIO_VIDEO_CAMCORDER:
1430                 return "AUDIO_VIDEO_CAMCORDER";
1431             case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_MONITOR:
1432                 return "AUDIO_VIDEO_VIDEO_MONITOR";
1433             case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER:
1434                 return "AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER";
1435             case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_CONFERENCING:
1436                 return "AUDIO_VIDEO_VIDEO_CONFERENCING";
1437             case 0x0444:
1438                 return "AUDIO_VIDEO_RESERVED_0x0444"; // uncommon
1439             case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_GAMING_TOY:
1440                 return "AUDIO_VIDEO_VIDEO_GAMING_TOY";
1441             default: // other device classes printed as a hex string.
1442                 return TextUtils.formatSimple("0x%04x", btDeviceClass);
1443         }
1444     }
1445 
1446     //------------------------------------------------------------
dump(PrintWriter pw, String prefix)1447     /*package*/ void dump(PrintWriter pw, String prefix) {
1448         pw.println("\n" + prefix + "mBluetoothHeadset: " + mBluetoothHeadset);
1449         pw.println(prefix + "mBluetoothHeadsetDevice: " + mBluetoothHeadsetDevice);
1450         if (mBluetoothHeadsetDevice != null) {
1451             final BluetoothClass bluetoothClass = mBluetoothHeadsetDevice.getBluetoothClass();
1452             if (bluetoothClass != null) {
1453                 pw.println(prefix + "mBluetoothHeadsetDevice.DeviceClass: "
1454                         + btDeviceClassToString(bluetoothClass.getDeviceClass()));
1455             }
1456         }
1457         pw.println(prefix + "mScoAudioState: " + scoAudioStateToString(mScoAudioState));
1458         pw.println(prefix + "mScoAudioMode: " + scoAudioModeToString(mScoAudioMode));
1459         pw.println("\n" + prefix + "mHearingAid: " + mHearingAid);
1460         pw.println("\n" + prefix + "mLeAudio: " + mLeAudio);
1461         pw.println(prefix + "mA2dp: " + mA2dp);
1462         pw.println(prefix + "mAvrcpAbsVolSupported: " + mAvrcpAbsVolSupported);
1463     }
1464 
1465 }
1466