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