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