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