• 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 android.annotation.NonNull;
19 import android.annotation.Nullable;
20 import android.bluetooth.BluetoothA2dp;
21 import android.bluetooth.BluetoothAdapter;
22 import android.bluetooth.BluetoothClass;
23 import android.bluetooth.BluetoothCodecConfig;
24 import android.bluetooth.BluetoothCodecStatus;
25 import android.bluetooth.BluetoothDevice;
26 import android.bluetooth.BluetoothHeadset;
27 import android.bluetooth.BluetoothHearingAid;
28 import android.bluetooth.BluetoothProfile;
29 import android.content.Intent;
30 import android.media.AudioManager;
31 import android.media.AudioSystem;
32 import android.os.Binder;
33 import android.os.IBinder;
34 import android.os.RemoteException;
35 import android.os.UserHandle;
36 import android.provider.Settings;
37 import android.util.Log;
38 
39 import com.android.internal.annotations.GuardedBy;
40 
41 import java.io.PrintWriter;
42 import java.util.ArrayList;
43 import java.util.List;
44 import java.util.NoSuchElementException;
45 import java.util.Objects;
46 
47 /**
48  * @hide
49  * Class to encapsulate all communication with Bluetooth services
50  */
51 public class BtHelper {
52 
53     private static final String TAG = "AS.BtHelper";
54 
55     private final @NonNull AudioDeviceBroker mDeviceBroker;
56 
BtHelper(@onNull AudioDeviceBroker broker)57     BtHelper(@NonNull AudioDeviceBroker broker) {
58         mDeviceBroker = broker;
59     }
60 
61     // List of clients having issued a SCO start request
62     @GuardedBy("BtHelper.this")
63     private final @NonNull ArrayList<ScoClient> mScoClients = new ArrayList<ScoClient>();
64 
65     // BluetoothHeadset API to control SCO connection
66     private @Nullable BluetoothHeadset mBluetoothHeadset;
67 
68     // Bluetooth headset device
69     private @Nullable BluetoothDevice mBluetoothHeadsetDevice;
70 
71     private @Nullable BluetoothHearingAid mHearingAid;
72 
73     // Reference to BluetoothA2dp to query for AbsoluteVolume.
74     private @Nullable BluetoothA2dp mA2dp;
75 
76     // If absolute volume is supported in AVRCP device
77     private boolean mAvrcpAbsVolSupported = false;
78 
79     // Current connection state indicated by bluetooth headset
80     private int mScoConnectionState;
81 
82     // Indicate if SCO audio connection is currently active and if the initiator is
83     // audio service (internal) or bluetooth headset (external)
84     private int mScoAudioState;
85 
86     // Indicates the mode used for SCO audio connection. The mode is virtual call if the request
87     // originated from an app targeting an API version before JB MR2 and raw audio after that.
88     private int mScoAudioMode;
89 
90     // SCO audio state is not active
91     private static final int SCO_STATE_INACTIVE = 0;
92     // SCO audio activation request waiting for headset service to connect
93     private static final int SCO_STATE_ACTIVATE_REQ = 1;
94     // SCO audio state is active due to an action in BT handsfree (either voice recognition or
95     // in call audio)
96     private static final int SCO_STATE_ACTIVE_EXTERNAL = 2;
97     // SCO audio state is active or starting due to a request from AudioManager API
98     private static final int SCO_STATE_ACTIVE_INTERNAL = 3;
99     // SCO audio deactivation request waiting for headset service to connect
100     private static final int SCO_STATE_DEACTIVATE_REQ = 4;
101     // SCO audio deactivation in progress, waiting for Bluetooth audio intent
102     private static final int SCO_STATE_DEACTIVATING = 5;
103 
104     // SCO audio mode is undefined
105     /*package*/  static final int SCO_MODE_UNDEFINED = -1;
106     // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall())
107     /*package*/  static final int SCO_MODE_VIRTUAL_CALL = 0;
108     // SCO audio mode is raw audio (BluetoothHeadset.connectAudio())
109     private  static final int SCO_MODE_RAW = 1;
110     // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition())
111     private  static final int SCO_MODE_VR = 2;
112     // max valid SCO audio mode values
113     private static final int SCO_MODE_MAX = 2;
114 
115     private static final int BT_HEARING_AID_GAIN_MIN = -128;
116 
117     /**
118      * Returns a string representation of the scoAudioMode.
119      */
scoAudioModeToString(int scoAudioMode)120     public static String scoAudioModeToString(int scoAudioMode) {
121         switch (scoAudioMode) {
122             case SCO_MODE_UNDEFINED:
123                 return "SCO_MODE_UNDEFINED";
124             case SCO_MODE_VIRTUAL_CALL:
125                 return "SCO_MODE_VIRTUAL_CALL";
126             case SCO_MODE_RAW:
127                 return "SCO_MODE_RAW";
128             case SCO_MODE_VR:
129                 return "SCO_MODE_VR";
130             default:
131                 return "SCO_MODE_(" + scoAudioMode + ")";
132         }
133     }
134 
135     /**
136      * Returns a string representation of the scoAudioState.
137      */
scoAudioStateToString(int scoAudioState)138     public static String scoAudioStateToString(int scoAudioState) {
139         switch (scoAudioState) {
140             case SCO_STATE_INACTIVE:
141                 return "SCO_STATE_INACTIVE";
142             case SCO_STATE_ACTIVATE_REQ:
143                 return "SCO_STATE_ACTIVATE_REQ";
144             case SCO_STATE_ACTIVE_EXTERNAL:
145                 return "SCO_STATE_ACTIVE_EXTERNAL";
146             case SCO_STATE_ACTIVE_INTERNAL:
147                 return "SCO_STATE_ACTIVE_INTERNAL";
148             case SCO_STATE_DEACTIVATING:
149                 return "SCO_STATE_DEACTIVATING";
150             default:
151                 return "SCO_STATE_(" + scoAudioState + ")";
152         }
153     }
154 
155     //----------------------------------------------------------------------
156     /*package*/ static class BluetoothA2dpDeviceInfo {
157         private final @NonNull BluetoothDevice mBtDevice;
158         private final int mVolume;
159         private final @AudioSystem.AudioFormatNativeEnumForBtCodec int mCodec;
160 
BluetoothA2dpDeviceInfo(@onNull BluetoothDevice btDevice)161         BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice) {
162             this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT);
163         }
164 
BluetoothA2dpDeviceInfo(@onNull BluetoothDevice btDevice, int volume, int codec)165         BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice, int volume, int codec) {
166             mBtDevice = btDevice;
167             mVolume = volume;
168             mCodec = codec;
169         }
170 
getBtDevice()171         public @NonNull BluetoothDevice getBtDevice() {
172             return mBtDevice;
173         }
174 
getVolume()175         public int getVolume() {
176             return mVolume;
177         }
178 
getCodec()179         public @AudioSystem.AudioFormatNativeEnumForBtCodec int getCodec() {
180             return mCodec;
181         }
182 
183         // redefine equality op so we can match messages intended for this device
184         @Override
equals(Object o)185         public boolean equals(Object o) {
186             if (o == null) {
187                 return false;
188             }
189             if (this == o) {
190                 return true;
191             }
192             if (o instanceof BluetoothA2dpDeviceInfo) {
193                 return mBtDevice.equals(((BluetoothA2dpDeviceInfo) o).getBtDevice());
194             }
195             return false;
196         }
197 
198 
199     }
200 
201     // A2DP device events
202     /*package*/ static final int EVENT_DEVICE_CONFIG_CHANGE = 0;
203     /*package*/ static final int EVENT_ACTIVE_DEVICE_CHANGE = 1;
204 
a2dpDeviceEventToString(int event)205     /*package*/ static String a2dpDeviceEventToString(int event) {
206         switch (event) {
207             case EVENT_DEVICE_CONFIG_CHANGE: return "DEVICE_CONFIG_CHANGE";
208             case EVENT_ACTIVE_DEVICE_CHANGE: return "ACTIVE_DEVICE_CHANGE";
209             default:
210                 return new String("invalid event:" + event);
211         }
212     }
213 
getName(@onNull BluetoothDevice device)214     /*package*/ @NonNull static String getName(@NonNull BluetoothDevice device) {
215         final String deviceName = device.getName();
216         if (deviceName == null) {
217             return "";
218         }
219         return deviceName;
220     }
221 
222     //----------------------------------------------------------------------
223     // Interface for AudioDeviceBroker
224 
225     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
226     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
onSystemReady()227     /*package*/ synchronized void onSystemReady() {
228         mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR;
229         resetBluetoothSco();
230         getBluetoothHeadset();
231 
232         //FIXME: this is to maintain compatibility with deprecated intent
233         // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
234         Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
235         newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE,
236                 AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
237         sendStickyBroadcastToAll(newIntent);
238 
239         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
240         if (adapter != null) {
241             adapter.getProfileProxy(mDeviceBroker.getContext(),
242                     mBluetoothProfileServiceListener, BluetoothProfile.A2DP);
243             adapter.getProfileProxy(mDeviceBroker.getContext(),
244                     mBluetoothProfileServiceListener, BluetoothProfile.HEARING_AID);
245         }
246     }
247 
onAudioServerDiedRestoreA2dp()248     /*package*/ synchronized void onAudioServerDiedRestoreA2dp() {
249         final int forMed = mDeviceBroker.getBluetoothA2dpEnabled()
250                 ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP;
251         mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, forMed, "onAudioServerDied()");
252     }
253 
isAvrcpAbsoluteVolumeSupported()254     /*package*/ synchronized boolean isAvrcpAbsoluteVolumeSupported() {
255         return (mA2dp != null && mAvrcpAbsVolSupported);
256     }
257 
setAvrcpAbsoluteVolumeSupported(boolean supported)258     /*package*/ synchronized void setAvrcpAbsoluteVolumeSupported(boolean supported) {
259         mAvrcpAbsVolSupported = supported;
260         Log.i(TAG, "setAvrcpAbsoluteVolumeSupported supported=" + supported);
261     }
262 
setAvrcpAbsoluteVolumeIndex(int index)263     /*package*/ synchronized void setAvrcpAbsoluteVolumeIndex(int index) {
264         if (mA2dp == null) {
265             if (AudioService.DEBUG_VOL) {
266                 AudioService.sVolumeLogger.log(new AudioEventLogger.StringEvent(
267                         "setAvrcpAbsoluteVolumeIndex: bailing due to null mA2dp").printLog(TAG));
268                 return;
269             }
270         }
271         if (!mAvrcpAbsVolSupported) {
272             AudioService.sVolumeLogger.log(new AudioEventLogger.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.log(new AudioServiceEvents.VolumeEvent(
280                 AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index));
281         mA2dp.setAvrcpAbsoluteVolume(index);
282     }
283 
getA2dpCodec( @onNull BluetoothDevice device)284     /*package*/ synchronized @AudioSystem.AudioFormatNativeEnumForBtCodec int getA2dpCodec(
285             @NonNull BluetoothDevice device) {
286         if (mA2dp == null) {
287             return AudioSystem.AUDIO_FORMAT_DEFAULT;
288         }
289         final BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device);
290         if (btCodecStatus == null) {
291             return AudioSystem.AUDIO_FORMAT_DEFAULT;
292         }
293         final BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig();
294         if (btCodecConfig == null) {
295             return AudioSystem.AUDIO_FORMAT_DEFAULT;
296         }
297         return AudioSystem.bluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
298     }
299 
300     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
301     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
receiveBtEvent(Intent intent)302     /*package*/ synchronized void receiveBtEvent(Intent intent) {
303         final String action = intent.getAction();
304         if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
305             BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
306             setBtScoActiveDevice(btDevice);
307         } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
308             boolean broadcast = false;
309             int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
310             int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
311             // broadcast intent if the connection was initated by AudioService
312             if (!mScoClients.isEmpty()
313                     && (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL
314                     || mScoAudioState == SCO_STATE_ACTIVATE_REQ
315                     || mScoAudioState == SCO_STATE_DEACTIVATE_REQ
316                     || mScoAudioState == SCO_STATE_DEACTIVATING)) {
317                 broadcast = true;
318             }
319             switch (btState) {
320                 case BluetoothHeadset.STATE_AUDIO_CONNECTED:
321                     scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
322                     if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
323                             && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
324                         mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
325                     }
326                     mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent");
327                     break;
328                 case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
329                     mDeviceBroker.setBluetoothScoOn(false, "BtHelper.receiveBtEvent");
330                     scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
331                     // There are two cases where we want to immediately reconnect audio:
332                     // 1) If a new start request was received while disconnecting: this was
333                     // notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ.
334                     // 2) If audio was connected then disconnected via Bluetooth APIs and
335                     // we still have pending activation requests by apps: this is indicated by
336                     // state SCO_STATE_ACTIVE_EXTERNAL and the mScoClients list not empty.
337                     if (mScoAudioState == SCO_STATE_ACTIVATE_REQ
338                             || (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL
339                                     && !mScoClients.isEmpty())) {
340                         if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
341                                 && connectBluetoothScoAudioHelper(mBluetoothHeadset,
342                                 mBluetoothHeadsetDevice, mScoAudioMode)) {
343                             mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
344                             broadcast = false;
345                             break;
346                         }
347                     }
348                     // Tear down SCO if disconnected from external
349                     if (mScoAudioState == SCO_STATE_DEACTIVATING) {
350                         clearAllScoClients(0, false);
351                     }
352                     mScoAudioState = SCO_STATE_INACTIVE;
353                     break;
354                 case BluetoothHeadset.STATE_AUDIO_CONNECTING:
355                     if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
356                             && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
357                         mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
358                     }
359                     broadcast = false;
360                     break;
361                 default:
362                     // do not broadcast CONNECTING or invalid state
363                     broadcast = false;
364                     break;
365             }
366             if (broadcast) {
367                 broadcastScoConnectionState(scoAudioState);
368                 //FIXME: this is to maintain compatibility with deprecated intent
369                 // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
370                 Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
371                 newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState);
372                 sendStickyBroadcastToAll(newIntent);
373             }
374         }
375     }
376 
377     /**
378      *
379      * @return false if SCO isn't connected
380      */
isBluetoothScoOn()381     /*package*/ synchronized boolean isBluetoothScoOn() {
382         if (mBluetoothHeadset == null) {
383             return false;
384         }
385         return mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
386                 == BluetoothHeadset.STATE_AUDIO_CONNECTED;
387     }
388 
389     /**
390      * Disconnect all SCO connections started by {@link AudioManager} except those started by
391      * {@param exceptPid}
392      *
393      * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept
394      */
395     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
396     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
disconnectBluetoothSco(int exceptPid)397     /*package*/ synchronized void disconnectBluetoothSco(int exceptPid) {
398         checkScoAudioState();
399         if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) {
400             return;
401         }
402         clearAllScoClients(exceptPid, true);
403     }
404 
405     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
406     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
startBluetoothScoForClient(IBinder cb, int scoAudioMode, @NonNull String eventSource)407     /*package*/ synchronized void startBluetoothScoForClient(IBinder cb, int scoAudioMode,
408                 @NonNull String eventSource) {
409         ScoClient client = getScoClient(cb, true);
410         // The calling identity must be cleared before calling ScoClient.incCount().
411         // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
412         // and this must be done on behalf of system server to make sure permissions are granted.
413         // The caller identity must be cleared after getScoClient() because it is needed if a new
414         // client is created.
415         final long ident = Binder.clearCallingIdentity();
416         try {
417             AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
418             client.requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
419         } catch (NullPointerException e) {
420             Log.e(TAG, "Null ScoClient", e);
421         }
422         Binder.restoreCallingIdentity(ident);
423     }
424 
425     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
426     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
stopBluetoothScoForClient(IBinder cb, @NonNull String eventSource)427     /*package*/ synchronized void stopBluetoothScoForClient(IBinder cb,
428             @NonNull String eventSource) {
429         ScoClient client = getScoClient(cb, false);
430         // The calling identity must be cleared before calling ScoClient.decCount().
431         // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
432         // and this must be done on behalf of system server to make sure permissions are granted.
433         final long ident = Binder.clearCallingIdentity();
434         if (client != null) {
435             stopAndRemoveClient(client, eventSource);
436         }
437         Binder.restoreCallingIdentity(ident);
438     }
439 
440     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
441     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
stopBluetoothScoForPid(int pid)442     /*package*/ synchronized void stopBluetoothScoForPid(int pid) {
443         ScoClient client = getScoClientForPid(pid);
444         if (client == null) {
445             return;
446         }
447         final String eventSource = new StringBuilder("stopBluetoothScoForPid(")
448                 .append(pid).append(")").toString();
449         stopAndRemoveClient(client, eventSource);
450     }
451 
452     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
453     // @GuardedBy("BtHelper.this")
stopAndRemoveClient(ScoClient client, @NonNull String eventSource)454     private void stopAndRemoveClient(ScoClient client, @NonNull String eventSource) {
455         AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
456         client.requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
457                 SCO_MODE_VIRTUAL_CALL);
458         // If a disconnection is pending, the client will be removed when clearAllScoClients()
459         // is called form receiveBtEvent()
460         if (mScoAudioState != SCO_STATE_DEACTIVATE_REQ
461                 && mScoAudioState != SCO_STATE_DEACTIVATING) {
462             client.remove(false /*stop */, true /*unregister*/);
463         }
464     }
465 
setHearingAidVolume(int index, int streamType)466     /*package*/ synchronized void setHearingAidVolume(int index, int streamType) {
467         if (mHearingAid == null) {
468             if (AudioService.DEBUG_VOL) {
469                 Log.i(TAG, "setHearingAidVolume: null mHearingAid");
470             }
471             return;
472         }
473         //hearing aid expect volume value in range -128dB to 0dB
474         int gainDB = (int) AudioSystem.getStreamVolumeDB(streamType, index / 10,
475                 AudioSystem.DEVICE_OUT_HEARING_AID);
476         if (gainDB < BT_HEARING_AID_GAIN_MIN) {
477             gainDB = BT_HEARING_AID_GAIN_MIN;
478         }
479         if (AudioService.DEBUG_VOL) {
480             Log.i(TAG, "setHearingAidVolume: calling mHearingAid.setVolume idx="
481                     + index + " gain=" + gainDB);
482         }
483         AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
484                 AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB));
485         mHearingAid.setVolume(gainDB);
486     }
487 
onBroadcastScoConnectionState(int state)488     /*package*/ synchronized void onBroadcastScoConnectionState(int state) {
489         if (state == mScoConnectionState) {
490             return;
491         }
492         Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
493         newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state);
494         newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE,
495                 mScoConnectionState);
496         sendStickyBroadcastToAll(newIntent);
497         mScoConnectionState = state;
498     }
499 
disconnectAllBluetoothProfiles()500     /*package*/ synchronized void disconnectAllBluetoothProfiles() {
501         mDeviceBroker.postDisconnectA2dp();
502         mDeviceBroker.postDisconnectA2dpSink();
503         mDeviceBroker.postDisconnectHeadset();
504         mDeviceBroker.postDisconnectHearingAid();
505     }
506 
507     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
508     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
resetBluetoothSco()509     /*package*/ synchronized void resetBluetoothSco() {
510         clearAllScoClients(0, false);
511         mScoAudioState = SCO_STATE_INACTIVE;
512         broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
513         AudioSystem.setParameters("A2dpSuspended=false");
514         mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
515     }
516 
517     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
518     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
disconnectHeadset()519     /*package*/ synchronized void disconnectHeadset() {
520         setBtScoActiveDevice(null);
521         mBluetoothHeadset = null;
522     }
523 
onA2dpProfileConnected(BluetoothA2dp a2dp)524     /*package*/ synchronized void onA2dpProfileConnected(BluetoothA2dp a2dp) {
525         mA2dp = a2dp;
526         final List<BluetoothDevice> deviceList = mA2dp.getConnectedDevices();
527         if (deviceList.isEmpty()) {
528             return;
529         }
530         final BluetoothDevice btDevice = deviceList.get(0);
531         // the device is guaranteed CONNECTED
532         mDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(btDevice,
533                 BluetoothA2dp.STATE_CONNECTED, BluetoothProfile.A2DP_SINK, true, -1);
534     }
535 
onA2dpSinkProfileConnected(BluetoothProfile profile)536     /*package*/ synchronized void onA2dpSinkProfileConnected(BluetoothProfile profile) {
537         final List<BluetoothDevice> deviceList = profile.getConnectedDevices();
538         if (deviceList.isEmpty()) {
539             return;
540         }
541         final BluetoothDevice btDevice = deviceList.get(0);
542         final @BluetoothProfile.BtProfileState int state =
543                 profile.getConnectionState(btDevice);
544         mDeviceBroker.postSetA2dpSourceConnectionState(
545                 state, new BluetoothA2dpDeviceInfo(btDevice));
546     }
547 
onHearingAidProfileConnected(BluetoothHearingAid hearingAid)548     /*package*/ synchronized void onHearingAidProfileConnected(BluetoothHearingAid hearingAid) {
549         mHearingAid = hearingAid;
550         final List<BluetoothDevice> deviceList = mHearingAid.getConnectedDevices();
551         if (deviceList.isEmpty()) {
552             return;
553         }
554         final BluetoothDevice btDevice = deviceList.get(0);
555         final @BluetoothProfile.BtProfileState int state =
556                 mHearingAid.getConnectionState(btDevice);
557         mDeviceBroker.postBluetoothHearingAidDeviceConnectionState(
558                 btDevice, state,
559                 /*suppressNoisyIntent*/ false,
560                 /*musicDevice*/ android.media.AudioSystem.DEVICE_NONE,
561                 /*eventSource*/ "mBluetoothProfileServiceListener");
562     }
563 
564     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
565     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
onHeadsetProfileConnected(BluetoothHeadset headset)566     /*package*/ synchronized void onHeadsetProfileConnected(BluetoothHeadset headset) {
567         // Discard timeout message
568         mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService();
569         mBluetoothHeadset = headset;
570         setBtScoActiveDevice(mBluetoothHeadset.getActiveDevice());
571         // Refresh SCO audio state
572         checkScoAudioState();
573         if (mScoAudioState != SCO_STATE_ACTIVATE_REQ
574                 && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
575             return;
576         }
577         boolean status = false;
578         if (mBluetoothHeadsetDevice != null) {
579             switch (mScoAudioState) {
580                 case SCO_STATE_ACTIVATE_REQ:
581                     status = connectBluetoothScoAudioHelper(
582                             mBluetoothHeadset,
583                             mBluetoothHeadsetDevice, mScoAudioMode);
584                     if (status) {
585                         mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
586                     }
587                     break;
588                 case SCO_STATE_DEACTIVATE_REQ:
589                     status = disconnectBluetoothScoAudioHelper(
590                             mBluetoothHeadset,
591                             mBluetoothHeadsetDevice, mScoAudioMode);
592                     if (status) {
593                         mScoAudioState = SCO_STATE_DEACTIVATING;
594                     }
595                     break;
596             }
597         }
598         if (!status) {
599             mScoAudioState = SCO_STATE_INACTIVE;
600             broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
601         }
602     }
603 
604     //----------------------------------------------------------------------
broadcastScoConnectionState(int state)605     private void broadcastScoConnectionState(int state) {
606         mDeviceBroker.postBroadcastScoConnectionState(state);
607     }
608 
handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive)609     private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) {
610         if (btDevice == null) {
611             return true;
612         }
613         String address = btDevice.getAddress();
614         BluetoothClass btClass = btDevice.getBluetoothClass();
615         int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
616         int[] outDeviceTypes = {
617                 AudioSystem.DEVICE_OUT_BLUETOOTH_SCO,
618                 AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET,
619                 AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT
620         };
621         if (btClass != null) {
622             switch (btClass.getDeviceClass()) {
623                 case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
624                 case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
625                     outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET };
626                     break;
627                 case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
628                     outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT };
629                     break;
630             }
631         }
632         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
633             address = "";
634         }
635         String btDeviceName =  getName(btDevice);
636         boolean result = false;
637         if (isActive) {
638             result |= mDeviceBroker.handleDeviceConnection(
639                     isActive, outDeviceTypes[0], address, btDeviceName);
640         } else {
641             for (int outDeviceType : outDeviceTypes) {
642                 result |= mDeviceBroker.handleDeviceConnection(
643                         isActive, outDeviceType, address, btDeviceName);
644             }
645         }
646         // handleDeviceConnection() && result to make sure the method get executed
647         result = mDeviceBroker.handleDeviceConnection(
648                 isActive, inDevice, address, btDeviceName) && result;
649         return result;
650     }
651 
652     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
653     //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
654     @GuardedBy("BtHelper.this")
setBtScoActiveDevice(BluetoothDevice btDevice)655     private void setBtScoActiveDevice(BluetoothDevice btDevice) {
656         Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice);
657         final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
658         if (Objects.equals(btDevice, previousActiveDevice)) {
659             return;
660         }
661         if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) {
662             Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device "
663                     + previousActiveDevice);
664         }
665         if (!handleBtScoActiveDeviceChange(btDevice, true)) {
666             Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice);
667             // set mBluetoothHeadsetDevice to null when failing to add new device
668             btDevice = null;
669         }
670         mBluetoothHeadsetDevice = btDevice;
671         if (mBluetoothHeadsetDevice == null) {
672             resetBluetoothSco();
673         }
674     }
675 
676     // NOTE this listener is NOT called from AudioDeviceBroker event thread, only call async
677     //      methods inside listener.
678     private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
679             new BluetoothProfile.ServiceListener() {
680                 public void onServiceConnected(int profile, BluetoothProfile proxy) {
681                     switch(profile) {
682                         case BluetoothProfile.A2DP:
683                             AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
684                                     "BT profile service: connecting A2DP profile"));
685                             mDeviceBroker.postBtA2dpProfileConnected((BluetoothA2dp) proxy);
686                             break;
687 
688                         case BluetoothProfile.A2DP_SINK:
689                             AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
690                                     "BT profile service: connecting A2DP_SINK profile"));
691                             mDeviceBroker.postBtA2dpSinkProfileConnected(proxy);
692                             break;
693 
694                         case BluetoothProfile.HEADSET:
695                             AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
696                                     "BT profile service: connecting HEADSET profile"));
697                             mDeviceBroker.postBtHeasetProfileConnected((BluetoothHeadset) proxy);
698                             break;
699 
700                         case BluetoothProfile.HEARING_AID:
701                             AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
702                                     "BT profile service: connecting HEARING_AID profile"));
703                             mDeviceBroker.postBtHearingAidProfileConnected(
704                                     (BluetoothHearingAid) proxy);
705                             break;
706                         default:
707                             break;
708                     }
709                 }
710                 public void onServiceDisconnected(int profile) {
711 
712                     switch (profile) {
713                         case BluetoothProfile.A2DP:
714                             mDeviceBroker.postDisconnectA2dp();
715                             break;
716 
717                         case BluetoothProfile.A2DP_SINK:
718                             mDeviceBroker.postDisconnectA2dpSink();
719                             break;
720 
721                         case BluetoothProfile.HEADSET:
722                             mDeviceBroker.postDisconnectHeadset();
723                             break;
724 
725                         case BluetoothProfile.HEARING_AID:
726                             mDeviceBroker.postDisconnectHearingAid();
727                             break;
728 
729                         default:
730                             break;
731                     }
732                 }
733             };
734 
735     //----------------------------------------------------------------------
736     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
737     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
scoClientDied(Object obj)738     /*package*/ synchronized void scoClientDied(Object obj) {
739         final ScoClient client = (ScoClient) obj;
740         client.remove(true /*stop*/, false /*unregister*/);
741         Log.w(TAG, "SCO client died");
742     }
743 
744     private class ScoClient implements IBinder.DeathRecipient {
745         private IBinder mCb; // To be notified of client's death
746         private int mCreatorPid;
747 
ScoClient(IBinder cb)748         ScoClient(IBinder cb) {
749             mCb = cb;
750             mCreatorPid = Binder.getCallingPid();
751         }
752 
registerDeathRecipient()753         public void registerDeathRecipient() {
754             try {
755                 mCb.linkToDeath(this, 0);
756             } catch (RemoteException e) {
757                 Log.w(TAG, "ScoClient could not link to " + mCb + " binder death");
758             }
759         }
760 
unregisterDeathRecipient()761         public void unregisterDeathRecipient() {
762             try {
763                 mCb.unlinkToDeath(this, 0);
764             } catch (NoSuchElementException e) {
765                 Log.w(TAG, "ScoClient could not not unregistered to binder");
766             }
767         }
768 
769         @Override
binderDied()770         public void binderDied() {
771             // process this from DeviceBroker's message queue to take the right locks since
772             // this event can impact SCO mode and requires querying audio mode stack
773             mDeviceBroker.postScoClientDied(this);
774         }
775 
getBinder()776         IBinder getBinder() {
777             return mCb;
778         }
779 
getPid()780         int getPid() {
781             return mCreatorPid;
782         }
783 
784         // @GuardedBy("AudioDeviceBroker.mSetModeLock")
785         //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
786         @GuardedBy("BtHelper.this")
requestScoState(int state, int scoAudioMode)787         private boolean requestScoState(int state, int scoAudioMode) {
788             checkScoAudioState();
789             if (mScoClients.size() != 1) {
790                 Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode
791                         + ", num SCO clients=" + mScoClients.size());
792                 return true;
793             }
794             if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
795                 // Make sure that the state transitions to CONNECTING even if we cannot initiate
796                 // the connection.
797                 broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
798                 // Accept SCO audio activation only in NORMAL audio mode or if the mode is
799                 // currently controlled by the same client process.
800                 final int modeOwnerPid =  mDeviceBroker.getModeOwnerPid();
801                 if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) {
802                     Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid "
803                             + modeOwnerPid + " != creatorPid " + mCreatorPid);
804                     broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
805                     return false;
806                 }
807                 switch (mScoAudioState) {
808                     case SCO_STATE_INACTIVE:
809                         mScoAudioMode = scoAudioMode;
810                         if (scoAudioMode == SCO_MODE_UNDEFINED) {
811                             mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
812                             if (mBluetoothHeadsetDevice != null) {
813                                 mScoAudioMode = Settings.Global.getInt(
814                                         mDeviceBroker.getContentResolver(),
815                                         "bluetooth_sco_channel_"
816                                                 + mBluetoothHeadsetDevice.getAddress(),
817                                         SCO_MODE_VIRTUAL_CALL);
818                                 if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) {
819                                     mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
820                                 }
821                             }
822                         }
823                         if (mBluetoothHeadset == null) {
824                             if (getBluetoothHeadset()) {
825                                 mScoAudioState = SCO_STATE_ACTIVATE_REQ;
826                             } else {
827                                 Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
828                                         + " connection, mScoAudioMode=" + mScoAudioMode);
829                                 broadcastScoConnectionState(
830                                         AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
831                                 return false;
832                             }
833                             break;
834                         }
835                         if (mBluetoothHeadsetDevice == null) {
836                             Log.w(TAG, "requestScoState: no active device while connecting,"
837                                     + " mScoAudioMode=" + mScoAudioMode);
838                             broadcastScoConnectionState(
839                                     AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
840                             return false;
841                         }
842                         if (connectBluetoothScoAudioHelper(mBluetoothHeadset,
843                                 mBluetoothHeadsetDevice, mScoAudioMode)) {
844                             mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
845                         } else {
846                             Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice
847                                     + " failed, mScoAudioMode=" + mScoAudioMode);
848                             broadcastScoConnectionState(
849                                     AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
850                             return false;
851                         }
852                         break;
853                     case SCO_STATE_DEACTIVATING:
854                         mScoAudioState = SCO_STATE_ACTIVATE_REQ;
855                         break;
856                     case SCO_STATE_DEACTIVATE_REQ:
857                         mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
858                         broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
859                         break;
860                     case SCO_STATE_ACTIVE_INTERNAL:
861                         Log.w(TAG, "requestScoState: already in ACTIVE mode, simply return");
862                         break;
863                     default:
864                         Log.w(TAG, "requestScoState: failed to connect in state "
865                                 + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
866                         broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
867                         return false;
868                 }
869             } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
870                 switch (mScoAudioState) {
871                     case SCO_STATE_ACTIVE_INTERNAL:
872                         if (mBluetoothHeadset == null) {
873                             if (getBluetoothHeadset()) {
874                                 mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
875                             } else {
876                                 Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
877                                         + " disconnection, mScoAudioMode=" + mScoAudioMode);
878                                 mScoAudioState = SCO_STATE_INACTIVE;
879                                 broadcastScoConnectionState(
880                                         AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
881                                 return false;
882                             }
883                             break;
884                         }
885                         if (mBluetoothHeadsetDevice == null) {
886                             mScoAudioState = SCO_STATE_INACTIVE;
887                             broadcastScoConnectionState(
888                                     AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
889                             break;
890                         }
891                         if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
892                                 mBluetoothHeadsetDevice, mScoAudioMode)) {
893                             mScoAudioState = SCO_STATE_DEACTIVATING;
894                         } else {
895                             mScoAudioState = SCO_STATE_INACTIVE;
896                             broadcastScoConnectionState(
897                                     AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
898                         }
899                         break;
900                     case SCO_STATE_ACTIVATE_REQ:
901                         mScoAudioState = SCO_STATE_INACTIVE;
902                         broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
903                         break;
904                     default:
905                         Log.w(TAG, "requestScoState: failed to disconnect in state "
906                                 + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
907                         broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
908                         return false;
909                 }
910             }
911             return true;
912         }
913 
914         @GuardedBy("BtHelper.this")
remove(boolean stop, boolean unregister)915         void remove(boolean stop, boolean unregister) {
916             if (unregister) {
917                 unregisterDeathRecipient();
918             }
919             if (stop) {
920                 requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
921                         SCO_MODE_VIRTUAL_CALL);
922             }
923             mScoClients.remove(this);
924         }
925     }
926 
927     //-----------------------------------------------------
928     // Utilities
sendStickyBroadcastToAll(Intent intent)929     private void sendStickyBroadcastToAll(Intent intent) {
930         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
931         final long ident = Binder.clearCallingIdentity();
932         try {
933             mDeviceBroker.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
934         } finally {
935             Binder.restoreCallingIdentity(ident);
936         }
937     }
938 
disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, BluetoothDevice device, int scoAudioMode)939     private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
940             BluetoothDevice device, int scoAudioMode) {
941         switch (scoAudioMode) {
942             case SCO_MODE_RAW:
943                 return bluetoothHeadset.disconnectAudio();
944             case SCO_MODE_VIRTUAL_CALL:
945                 return bluetoothHeadset.stopScoUsingVirtualVoiceCall();
946             case SCO_MODE_VR:
947                 return bluetoothHeadset.stopVoiceRecognition(device);
948             default:
949                 return false;
950         }
951     }
952 
connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, BluetoothDevice device, int scoAudioMode)953     private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
954             BluetoothDevice device, int scoAudioMode) {
955         switch (scoAudioMode) {
956             case SCO_MODE_RAW:
957                 return bluetoothHeadset.connectAudio();
958             case SCO_MODE_VIRTUAL_CALL:
959                 return bluetoothHeadset.startScoUsingVirtualVoiceCall();
960             case SCO_MODE_VR:
961                 return bluetoothHeadset.startVoiceRecognition(device);
962             default:
963                 return false;
964         }
965     }
966 
checkScoAudioState()967     private void checkScoAudioState() {
968         if (mBluetoothHeadset != null
969                 && mBluetoothHeadsetDevice != null
970                 && mScoAudioState == SCO_STATE_INACTIVE
971                 && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
972                 != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
973             mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
974         }
975     }
976 
977 
978     @GuardedBy("BtHelper.this")
getScoClient(IBinder cb, boolean create)979     private ScoClient getScoClient(IBinder cb, boolean create) {
980         for (ScoClient existingClient : mScoClients) {
981             if (existingClient.getBinder() == cb) {
982                 return existingClient;
983             }
984         }
985         if (create) {
986             ScoClient newClient = new ScoClient(cb);
987             newClient.registerDeathRecipient();
988             mScoClients.add(newClient);
989             return newClient;
990         }
991         return null;
992     }
993 
994     @GuardedBy("BtHelper.this")
getScoClientForPid(int pid)995     private ScoClient getScoClientForPid(int pid) {
996         for (ScoClient cl : mScoClients) {
997             if (cl.getPid() == pid) {
998                 return cl;
999             }
1000         }
1001         return null;
1002     }
1003 
1004     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
1005     //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
1006     @GuardedBy("BtHelper.this")
clearAllScoClients(int exceptPid, boolean stopSco)1007     private void clearAllScoClients(int exceptPid, boolean stopSco) {
1008         final ArrayList<ScoClient> clients = new ArrayList<ScoClient>();
1009         for (ScoClient cl : mScoClients) {
1010             if (cl.getPid() != exceptPid) {
1011                 clients.add(cl);
1012             }
1013         }
1014         for (ScoClient cl : clients) {
1015             cl.remove(stopSco, true /*unregister*/);
1016         }
1017 
1018     }
1019 
getBluetoothHeadset()1020     private boolean getBluetoothHeadset() {
1021         boolean result = false;
1022         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
1023         if (adapter != null) {
1024             result = adapter.getProfileProxy(mDeviceBroker.getContext(),
1025                     mBluetoothProfileServiceListener, BluetoothProfile.HEADSET);
1026         }
1027         // If we could not get a bluetooth headset proxy, send a failure message
1028         // without delay to reset the SCO audio state and clear SCO clients.
1029         // If we could get a proxy, send a delayed failure message that will reset our state
1030         // in case we don't receive onServiceConnected().
1031         mDeviceBroker.handleFailureToConnectToBtHeadsetService(
1032                 result ? AudioDeviceBroker.BT_HEADSET_CNCT_TIMEOUT_MS : 0);
1033         return result;
1034     }
1035 
1036     /**
1037      * Returns the String equivalent of the btCodecType.
1038      *
1039      * This uses an "ENCODING_" prefix for consistency with Audio;
1040      * we could alternately use the "SOURCE_CODEC_TYPE_" prefix from Bluetooth.
1041      */
bluetoothCodecToEncodingString(int btCodecType)1042     public static String bluetoothCodecToEncodingString(int btCodecType) {
1043         switch (btCodecType) {
1044             case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC:
1045                 return "ENCODING_SBC";
1046             case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC:
1047                 return "ENCODING_AAC";
1048             case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX:
1049                 return "ENCODING_APTX";
1050             case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD:
1051                 return "ENCODING_APTX_HD";
1052             case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC:
1053                 return "ENCODING_LDAC";
1054             default:
1055                 return "ENCODING_BT_CODEC_TYPE(" + btCodecType + ")";
1056         }
1057     }
1058 
1059     //------------------------------------------------------------
dump(PrintWriter pw, String prefix)1060     /*package*/ void dump(PrintWriter pw, String prefix) {
1061         pw.println("\n" + prefix + "mBluetoothHeadset: " + mBluetoothHeadset);
1062         pw.println(prefix + "mBluetoothHeadsetDevice: " + mBluetoothHeadsetDevice);
1063         pw.println(prefix + "mScoAudioState: " + scoAudioStateToString(mScoAudioState));
1064         pw.println(prefix + "mScoAudioMode: " + scoAudioModeToString(mScoAudioMode));
1065         pw.println(prefix + "Sco clients:");
1066         mScoClients.forEach((cl) -> {
1067             pw.println("  " + prefix + "pid: " + cl.getPid() + " cb: " + cl.getBinder()); });
1068 
1069         pw.println("\n" + prefix + "mHearingAid: " + mHearingAid);
1070         pw.println(prefix + "mA2dp: " + mA2dp);
1071         pw.println(prefix + "mAvrcpAbsVolSupported: " + mAvrcpAbsVolSupported);
1072     }
1073 
1074 }
1075