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