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 static android.bluetooth.BluetoothDevice.DEVICE_TYPE_DEFAULT; 19 import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET; 20 import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_WATCH; 21 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_CARKIT; 22 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES; 23 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEARING_AID; 24 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_RECEIVER; 25 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER; 26 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN; 27 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_WATCH; 28 29 import static com.android.media.audio.Flags.optimizeBtDeviceSwitch; 30 31 import android.annotation.NonNull; 32 import android.annotation.Nullable; 33 import android.bluetooth.BluetoothA2dp; 34 import android.bluetooth.BluetoothAdapter; 35 import android.bluetooth.BluetoothClass; 36 import android.bluetooth.BluetoothCodecConfig; 37 import android.bluetooth.BluetoothCodecStatus; 38 import android.bluetooth.BluetoothDevice; 39 import android.bluetooth.BluetoothHeadset; 40 import android.bluetooth.BluetoothHearingAid; 41 import android.bluetooth.BluetoothLeAudio; 42 import android.bluetooth.BluetoothLeAudioCodecConfig; 43 import android.bluetooth.BluetoothLeAudioCodecStatus; 44 import android.bluetooth.BluetoothProfile; 45 import android.content.Context; 46 import android.content.Intent; 47 import android.media.AudioDeviceAttributes; 48 import android.media.AudioManager; 49 import android.media.AudioManager.AudioDeviceCategory; 50 import android.media.AudioSystem; 51 import android.media.BluetoothProfileConnectionInfo; 52 import android.os.Binder; 53 import android.os.Bundle; 54 import android.os.UserHandle; 55 import android.provider.Settings; 56 import android.text.TextUtils; 57 import android.util.Log; 58 import android.util.Pair; 59 60 import com.android.internal.annotations.GuardedBy; 61 import com.android.server.utils.EventLogger; 62 63 import java.io.PrintWriter; 64 import java.util.ArrayList; 65 import java.util.HashMap; 66 import java.util.List; 67 import java.util.Map; 68 import java.util.Objects; 69 70 /** 71 * @hide 72 * Class to encapsulate all communication with Bluetooth services 73 */ 74 public class BtHelper { 75 76 private static final String TAG = "AS.BtHelper"; 77 78 private final @NonNull AudioDeviceBroker mDeviceBroker; 79 private final @NonNull Context mContext; 80 BtHelper(@onNull AudioDeviceBroker broker, Context context)81 BtHelper(@NonNull AudioDeviceBroker broker, Context context) { 82 mDeviceBroker = broker; 83 mContext = context; 84 } 85 86 // BluetoothHeadset API to control SCO connection 87 @GuardedBy("BtHelper.this") 88 private @Nullable BluetoothHeadset mBluetoothHeadset; 89 90 // Bluetooth headset device 91 @GuardedBy("mDeviceBroker.mDeviceStateLock") 92 private @Nullable BluetoothDevice mBluetoothHeadsetDevice; 93 94 @GuardedBy("mDeviceBroker.mDeviceStateLock") 95 private final Map<BluetoothDevice, AudioDeviceAttributes> mResolvedScoAudioDevices = 96 new HashMap<>(); 97 98 @GuardedBy("BtHelper.this") 99 private @Nullable BluetoothHearingAid mHearingAid = null; 100 101 @GuardedBy("BtHelper.this") 102 private @Nullable BluetoothLeAudio mLeAudio = null; 103 104 @GuardedBy("BtHelper.this") 105 private @Nullable BluetoothLeAudioCodecConfig mLeAudioCodecConfig; 106 107 // Reference to BluetoothA2dp to query for AbsoluteVolume. 108 @GuardedBy("BtHelper.this") 109 private @Nullable BluetoothA2dp mA2dp = null; 110 111 @GuardedBy("BtHelper.this") 112 private @Nullable BluetoothCodecConfig mA2dpCodecConfig; 113 114 @GuardedBy("BtHelper.this") 115 private @AudioSystem.AudioFormatNativeEnumForBtCodec 116 int mLeAudioBroadcastCodec = AudioSystem.AUDIO_FORMAT_DEFAULT; 117 118 // If absolute volume is supported in AVRCP device 119 @GuardedBy("mDeviceBroker.mDeviceStateLock") 120 private boolean mAvrcpAbsVolSupported = false; 121 122 // Current connection state indicated by bluetooth headset 123 @GuardedBy("mDeviceBroker.mDeviceStateLock") 124 private int mScoConnectionState; 125 126 // Indicate if SCO audio connection is currently active and if the initiator is 127 // audio service (internal) or bluetooth headset (external) 128 @GuardedBy("mDeviceBroker.mDeviceStateLock") 129 private int mScoAudioState; 130 131 // Indicates the mode used for SCO audio connection. The mode is virtual call if the request 132 // originated from an app targeting an API version before JB MR2 and raw audio after that. 133 @GuardedBy("mDeviceBroker.mDeviceStateLock") 134 private int mScoAudioMode; 135 136 // SCO audio state is not active 137 private static final int SCO_STATE_INACTIVE = 0; 138 // SCO audio activation request waiting for headset service to connect 139 private static final int SCO_STATE_ACTIVATE_REQ = 1; 140 // SCO audio state is active due to an action in BT handsfree (either voice recognition or 141 // in call audio) 142 private static final int SCO_STATE_ACTIVE_EXTERNAL = 2; 143 // SCO audio state is active or starting due to a request from AudioManager API 144 private static final int SCO_STATE_ACTIVE_INTERNAL = 3; 145 // SCO audio deactivation request waiting for headset service to connect 146 private static final int SCO_STATE_DEACTIVATE_REQ = 4; 147 // SCO audio deactivation in progress, waiting for Bluetooth audio intent 148 private static final int SCO_STATE_DEACTIVATING = 5; 149 150 // SCO audio mode is undefined 151 /*package*/ static final int SCO_MODE_UNDEFINED = -1; 152 // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall()) 153 /*package*/ static final int SCO_MODE_VIRTUAL_CALL = 0; 154 // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition()) 155 private static final int SCO_MODE_VR = 2; 156 // max valid SCO audio mode values 157 private static final int SCO_MODE_MAX = 2; 158 159 private static final int BT_HEARING_AID_GAIN_MIN = -128; 160 private static final int BT_LE_AUDIO_MAX_VOL = 255; 161 162 // BtDevice constants currently rolling out under flag protection. Use own 163 // constants instead to avoid mainline dependency from flag library import 164 // TODO(b/335936458): remove once the BtDevice flag is rolled out 165 private static final String DEVICE_TYPE_SPEAKER = "Speaker"; 166 private static final String DEVICE_TYPE_HEADSET = "Headset"; 167 private static final String DEVICE_TYPE_CARKIT = "Carkit"; 168 private static final String DEVICE_TYPE_HEARING_AID = "HearingAid"; 169 170 /** 171 * Returns a string representation of the scoAudioMode. 172 */ scoAudioModeToString(int scoAudioMode)173 public static String scoAudioModeToString(int scoAudioMode) { 174 switch (scoAudioMode) { 175 case SCO_MODE_UNDEFINED: 176 return "SCO_MODE_UNDEFINED"; 177 case SCO_MODE_VIRTUAL_CALL: 178 return "SCO_MODE_VIRTUAL_CALL"; 179 case SCO_MODE_VR: 180 return "SCO_MODE_VR"; 181 default: 182 return "SCO_MODE_(" + scoAudioMode + ")"; 183 } 184 } 185 186 /** 187 * Returns a string representation of the scoAudioState. 188 */ scoAudioStateToString(int scoAudioState)189 public static String scoAudioStateToString(int scoAudioState) { 190 switch (scoAudioState) { 191 case SCO_STATE_INACTIVE: 192 return "SCO_STATE_INACTIVE"; 193 case SCO_STATE_ACTIVATE_REQ: 194 return "SCO_STATE_ACTIVATE_REQ"; 195 case SCO_STATE_ACTIVE_EXTERNAL: 196 return "SCO_STATE_ACTIVE_EXTERNAL"; 197 case SCO_STATE_ACTIVE_INTERNAL: 198 return "SCO_STATE_ACTIVE_INTERNAL"; 199 case SCO_STATE_DEACTIVATING: 200 return "SCO_STATE_DEACTIVATING"; 201 default: 202 return "SCO_STATE_(" + scoAudioState + ")"; 203 } 204 } 205 206 // A2DP device events 207 /*package*/ static final int EVENT_DEVICE_CONFIG_CHANGE = 0; 208 deviceEventToString(int event)209 /*package*/ static String deviceEventToString(int event) { 210 switch (event) { 211 case EVENT_DEVICE_CONFIG_CHANGE: return "DEVICE_CONFIG_CHANGE"; 212 default: 213 return new String("invalid event:" + event); 214 } 215 } 216 getName(@onNull BluetoothDevice device)217 /*package*/ @NonNull static String getName(@NonNull BluetoothDevice device) { 218 final String deviceName = device.getName(); 219 if (deviceName == null) { 220 return ""; 221 } 222 return deviceName; 223 } 224 225 //---------------------------------------------------------------------- 226 // Interface for AudioDeviceBroker 227 228 @GuardedBy("mDeviceBroker.mDeviceStateLock") onSystemReady()229 /*package*/ synchronized void onSystemReady() { 230 mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR; 231 resetBluetoothSco(); 232 getBluetoothHeadset(); 233 234 //FIXME: this is to maintain compatibility with deprecated intent 235 // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate. 236 Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); 237 newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, 238 AudioManager.SCO_AUDIO_STATE_DISCONNECTED); 239 sendStickyBroadcastToAll(newIntent); 240 241 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 242 if (adapter != null) { 243 adapter.getProfileProxy(mDeviceBroker.getContext(), 244 mBluetoothProfileServiceListener, BluetoothProfile.A2DP); 245 adapter.getProfileProxy(mDeviceBroker.getContext(), 246 mBluetoothProfileServiceListener, BluetoothProfile.A2DP_SINK); 247 adapter.getProfileProxy(mDeviceBroker.getContext(), 248 mBluetoothProfileServiceListener, BluetoothProfile.HEARING_AID); 249 adapter.getProfileProxy(mDeviceBroker.getContext(), 250 mBluetoothProfileServiceListener, BluetoothProfile.LE_AUDIO); 251 adapter.getProfileProxy(mDeviceBroker.getContext(), 252 mBluetoothProfileServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST); 253 } 254 } 255 256 @GuardedBy("mDeviceBroker.mDeviceStateLock") setAvrcpAbsoluteVolumeSupported(boolean supported)257 /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) { 258 mAvrcpAbsVolSupported = supported; 259 Log.i(TAG, "setAvrcpAbsoluteVolumeSupported supported=" + supported); 260 } 261 262 @GuardedBy("mDeviceBroker.mDeviceStateLock") setAvrcpAbsoluteVolumeIndex(int index)263 /*package*/ synchronized void setAvrcpAbsoluteVolumeIndex(int index) { 264 if (mA2dp == null) { 265 if (AudioService.DEBUG_VOL) { 266 AudioService.sVolumeLogger.enqueue(new EventLogger.StringEvent( 267 "setAvrcpAbsoluteVolumeIndex: bailing due to null mA2dp").printLog(TAG)); 268 } 269 return; 270 } 271 if (!mAvrcpAbsVolSupported) { 272 AudioService.sVolumeLogger.enqueue(new EventLogger.StringEvent( 273 "setAvrcpAbsoluteVolumeIndex: abs vol not supported ").printLog(TAG)); 274 return; 275 } 276 if (AudioService.DEBUG_VOL) { 277 Log.i(TAG, "setAvrcpAbsoluteVolumeIndex index=" + index); 278 } 279 AudioService.sVolumeLogger.enqueue(new AudioServiceEvents.VolumeEvent( 280 AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index)); 281 try { 282 mA2dp.setAvrcpAbsoluteVolume(index); 283 } catch (Exception e) { 284 Log.e(TAG, "Exception while changing abs volume", e); 285 } 286 } 287 getCodec( @onNull BluetoothDevice device, @AudioService.BtProfile int profile)288 private synchronized Pair<Integer, Boolean> getCodec( 289 @NonNull BluetoothDevice device, @AudioService.BtProfile int profile) { 290 291 switch (profile) { 292 case BluetoothProfile.A2DP: { 293 boolean changed = mA2dpCodecConfig != null; 294 if (mA2dp == null) { 295 mA2dpCodecConfig = null; 296 return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed); 297 } 298 BluetoothCodecStatus btCodecStatus = null; 299 try { 300 btCodecStatus = mA2dp.getCodecStatus(device); 301 } catch (Exception e) { 302 Log.e(TAG, "Exception while getting status of " + device, e); 303 } 304 if (btCodecStatus == null) { 305 Log.e(TAG, "getCodec, null A2DP codec status for device: " + device); 306 mA2dpCodecConfig = null; 307 return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed); 308 } 309 final BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig(); 310 if (btCodecConfig == null) { 311 mA2dpCodecConfig = null; 312 return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed); 313 } 314 changed = !btCodecConfig.equals(mA2dpCodecConfig); 315 mA2dpCodecConfig = btCodecConfig; 316 return new Pair<>(AudioSystem.bluetoothA2dpCodecToAudioFormat( 317 btCodecConfig.getCodecType()), changed); 318 } 319 case BluetoothProfile.LE_AUDIO: { 320 boolean changed = mLeAudioCodecConfig != null; 321 if (mLeAudio == null) { 322 mLeAudioCodecConfig = null; 323 return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed); 324 } 325 BluetoothLeAudioCodecStatus btLeCodecStatus = null; 326 int groupId = mLeAudio.getGroupId(device); 327 try { 328 btLeCodecStatus = mLeAudio.getCodecStatus(groupId); 329 } catch (Exception e) { 330 Log.e(TAG, "Exception while getting status of " + device, e); 331 } 332 if (btLeCodecStatus == null) { 333 Log.e(TAG, "getCodec, null LE codec status for device: " + device); 334 mLeAudioCodecConfig = null; 335 return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed); 336 } 337 BluetoothLeAudioCodecConfig btLeCodecConfig = 338 btLeCodecStatus.getOutputCodecConfig(); 339 if (btLeCodecConfig == null) { 340 mLeAudioCodecConfig = null; 341 return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed); 342 } 343 changed = !btLeCodecConfig.equals(mLeAudioCodecConfig); 344 mLeAudioCodecConfig = btLeCodecConfig; 345 return new Pair<>(AudioSystem.bluetoothLeCodecToAudioFormat( 346 btLeCodecConfig.getCodecType()), changed); 347 } 348 case BluetoothProfile.LE_AUDIO_BROADCAST: { 349 // We assume LC3 for LE Audio broadcast codec as there is no API to get the codec 350 // config on LE Broadcast profile proxy. 351 boolean changed = mLeAudioBroadcastCodec != AudioSystem.AUDIO_FORMAT_LC3; 352 mLeAudioBroadcastCodec = AudioSystem.AUDIO_FORMAT_LC3; 353 return new Pair<>(mLeAudioBroadcastCodec, changed); 354 } 355 default: 356 return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, false); 357 } 358 } 359 360 /*package*/ synchronized Pair<Integer, Boolean> getCodecWithFallback(@onNull BluetoothDevice device, @AudioService.BtProfile int profile, boolean isLeOutput, @NonNull String source)361 getCodecWithFallback(@NonNull BluetoothDevice device, 362 @AudioService.BtProfile int profile, 363 boolean isLeOutput, @NonNull String source) { 364 // For profiles other than A2DP and LE Audio output, the audio codec format must be 365 // AUDIO_FORMAT_DEFAULT as native audio policy manager expects a specific audio format 366 // only if audio HW module selection based on format is supported for the device type. 367 if (!(profile == BluetoothProfile.A2DP 368 || (isLeOutput && ((profile == BluetoothProfile.LE_AUDIO) 369 || (profile == BluetoothProfile.LE_AUDIO_BROADCAST))))) { 370 return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, false); 371 } 372 Pair<Integer, Boolean> codecAndChanged = 373 getCodec(device, profile); 374 if (codecAndChanged.first == AudioSystem.AUDIO_FORMAT_DEFAULT) { 375 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( 376 "getCodec DEFAULT from " + source + " fallback to " 377 + (profile == BluetoothProfile.A2DP ? "SBC" : "LC3"))); 378 return new Pair<>(profile == BluetoothProfile.A2DP 379 ? AudioSystem.AUDIO_FORMAT_SBC : AudioSystem.AUDIO_FORMAT_LC3, true); 380 } 381 382 return codecAndChanged; 383 } 384 385 @GuardedBy("mDeviceBroker.mDeviceStateLock") onReceiveBtEvent(Intent intent)386 /*package*/ synchronized void onReceiveBtEvent(Intent intent) { 387 final String action = intent.getAction(); 388 389 if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) { 390 BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, 391 android.bluetooth.BluetoothDevice.class); 392 if (btDevice != null && !isProfilePoxyConnected(BluetoothProfile.HEADSET)) { 393 AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( 394 "onReceiveBtEvent ACTION_ACTIVE_DEVICE_CHANGED " 395 + "received with null profile proxy for device: " 396 + btDevice)).printLog(TAG)); 397 return; 398 399 } 400 boolean deviceSwitch = optimizeBtDeviceSwitch() 401 && btDevice != null && mBluetoothHeadsetDevice != null; 402 onSetBtScoActiveDevice(btDevice, deviceSwitch); 403 } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { 404 int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 405 onScoAudioStateChanged(btState); 406 } 407 } 408 409 /** 410 * Exclusively called from AudioDeviceBroker (with mDeviceStateLock held) 411 * when handling MSG_L_RECEIVED_BT_EVENT in {@link #onReceiveBtEvent(Intent)} 412 * as part of the serialization of the communication route selection 413 */ 414 @GuardedBy("mDeviceBroker.mDeviceStateLock") onScoAudioStateChanged(int state)415 private synchronized void onScoAudioStateChanged(int state) { 416 boolean broadcast = false; 417 int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR; 418 Log.i(TAG, "onScoAudioStateChanged state: " + state 419 + ", mScoAudioState: " + mScoAudioState); 420 switch (state) { 421 case BluetoothHeadset.STATE_AUDIO_CONNECTED: 422 scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; 423 if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL 424 && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { 425 mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; 426 } else if (mDeviceBroker.isBluetoothScoRequested()) { 427 // broadcast intent if the connection was initated by AudioService 428 broadcast = true; 429 } 430 if (!mDeviceBroker.isScoManagedByAudio()) { 431 mDeviceBroker.setBluetoothScoOn( 432 true, "BtHelper.onScoAudioStateChanged, state: " + state); 433 } 434 break; 435 case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: 436 if (!mDeviceBroker.isScoManagedByAudio()) { 437 mDeviceBroker.setBluetoothScoOn( 438 false, "BtHelper.onScoAudioStateChanged, state: " + state); 439 } 440 scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; 441 // There are two cases where we want to immediately reconnect audio: 442 // 1) If a new start request was received while disconnecting: this was 443 // notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ. 444 // 2) If audio was connected then disconnected via Bluetooth APIs and 445 // we still have pending activation requests by apps: this is indicated by 446 // state SCO_STATE_ACTIVE_EXTERNAL and BT SCO is requested. 447 if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) { 448 if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null 449 && connectBluetoothScoAudioHelper(mBluetoothHeadset, 450 mBluetoothHeadsetDevice, mScoAudioMode)) { 451 mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; 452 scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTING; 453 broadcast = true; 454 break; 455 } 456 } 457 if (mScoAudioState != SCO_STATE_ACTIVE_EXTERNAL) { 458 broadcast = true; 459 } 460 mScoAudioState = SCO_STATE_INACTIVE; 461 break; 462 case BluetoothHeadset.STATE_AUDIO_CONNECTING: 463 if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL 464 && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { 465 mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; 466 } 467 break; 468 default: 469 break; 470 } 471 if (broadcast) { 472 Log.i(TAG, "onScoAudioStateChanged broadcasting state: " + scoAudioState); 473 broadcastScoConnectionState(scoAudioState); 474 //FIXME: this is to maintain compatibility with deprecated intent 475 // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate. 476 Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); 477 newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState); 478 sendStickyBroadcastToAll(newIntent); 479 } 480 } 481 /** 482 * 483 * @return false if SCO isn't connected 484 */ 485 @GuardedBy("mDeviceBroker.mDeviceStateLock") isBluetoothScoOn()486 /*package*/ synchronized boolean isBluetoothScoOn() { 487 if (mBluetoothHeadset == null || mBluetoothHeadsetDevice == null) { 488 return false; 489 } 490 try { 491 return mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) 492 == BluetoothHeadset.STATE_AUDIO_CONNECTED; 493 } catch (Exception e) { 494 Log.e(TAG, "Exception while getting audio state of " + mBluetoothHeadsetDevice, e); 495 } 496 return false; 497 } 498 499 @GuardedBy("mDeviceBroker.mDeviceStateLock") isBluetoothScoRequestedInternally()500 /*package*/ boolean isBluetoothScoRequestedInternally() { 501 return mScoAudioState == SCO_STATE_ACTIVE_INTERNAL 502 || mScoAudioState == SCO_STATE_ACTIVATE_REQ; 503 } 504 505 @GuardedBy("mDeviceBroker.mDeviceStateLock") startBluetoothSco(int scoAudioMode, @NonNull String eventSource)506 /*package*/ synchronized boolean startBluetoothSco(int scoAudioMode, 507 @NonNull String eventSource) { 508 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource)); 509 return requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode); 510 } 511 512 @GuardedBy("mDeviceBroker.mDeviceStateLock") stopBluetoothSco(@onNull String eventSource)513 /*package*/ synchronized boolean stopBluetoothSco(@NonNull String eventSource) { 514 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource)); 515 return requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, SCO_MODE_VIRTUAL_CALL); 516 } 517 setLeAudioVolume(int index, int maxIndex, int streamType)518 /*package*/ synchronized void setLeAudioVolume(int index, int maxIndex, int streamType) { 519 if (mLeAudio == null) { 520 if (AudioService.DEBUG_VOL) { 521 Log.i(TAG, "setLeAudioVolume: null mLeAudio"); 522 } 523 return; 524 } 525 /* leaudio expect volume value in range 0 to 255 */ 526 int volume = (int) Math.round((double) index * BT_LE_AUDIO_MAX_VOL / maxIndex); 527 528 if (AudioService.DEBUG_VOL) { 529 Log.i(TAG, "setLeAudioVolume: calling mLeAudio.setVolume idx=" 530 + index + " volume=" + volume); 531 } 532 AudioService.sVolumeLogger.enqueue(new AudioServiceEvents.VolumeEvent( 533 AudioServiceEvents.VolumeEvent.VOL_SET_LE_AUDIO_VOL, streamType, index, 534 maxIndex, /*caller=*/null)); 535 try { 536 mLeAudio.setVolume(volume); 537 } catch (Exception e) { 538 Log.e(TAG, "Exception while setting LE volume", e); 539 } 540 } 541 setHearingAidVolume(int index, int streamType, boolean isHeadAidConnected)542 /*package*/ synchronized void setHearingAidVolume(int index, int streamType, 543 boolean isHeadAidConnected) { 544 if (mHearingAid == null) { 545 if (AudioService.DEBUG_VOL) { 546 Log.i(TAG, "setHearingAidVolume: null mHearingAid"); 547 } 548 return; 549 } 550 //hearing aid expect volume value in range -128dB to 0dB 551 int gainDB = (int) AudioSystem.getStreamVolumeDB(streamType, index / 10, 552 AudioSystem.DEVICE_OUT_HEARING_AID); 553 if (gainDB < BT_HEARING_AID_GAIN_MIN) { 554 gainDB = BT_HEARING_AID_GAIN_MIN; 555 } 556 if (AudioService.DEBUG_VOL) { 557 Log.i(TAG, "setHearingAidVolume: calling mHearingAid.setVolume idx=" 558 + index + " gain=" + gainDB); 559 } 560 // do not log when hearing aid is not connected to avoid confusion when reading dumpsys 561 if (isHeadAidConnected) { 562 AudioService.sVolumeLogger.enqueue(new AudioServiceEvents.VolumeEvent( 563 AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB)); 564 } 565 try { 566 mHearingAid.setVolume(gainDB); 567 } catch (Exception e) { 568 Log.i(TAG, "Exception while setting hearing aid volume", e); 569 } 570 } 571 572 @GuardedBy("mDeviceBroker.mDeviceStateLock") onBroadcastScoConnectionState(int state)573 /*package*/ void onBroadcastScoConnectionState(int state) { 574 if (state == mScoConnectionState) { 575 return; 576 } 577 Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); 578 newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state); 579 newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE, 580 mScoConnectionState); 581 sendStickyBroadcastToAll(newIntent); 582 mScoConnectionState = state; 583 } 584 585 @GuardedBy("mDeviceBroker.mDeviceStateLock") resetBluetoothSco()586 /*package*/ void resetBluetoothSco() { 587 mScoAudioState = SCO_STATE_INACTIVE; 588 broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); 589 mDeviceBroker.clearA2dpSuspended(false /* internalOnly */); 590 mDeviceBroker.clearLeAudioSuspended(false /* internalOnly */); 591 mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco"); 592 } 593 594 @GuardedBy("mDeviceBroker.mDeviceStateLock") onBtProfileDisconnected(int profile)595 /*package*/ synchronized void onBtProfileDisconnected(int profile) { 596 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( 597 "BT profile " + BluetoothProfile.getProfileName(profile) 598 + " disconnected").printLog(TAG)); 599 switch (profile) { 600 case BluetoothProfile.HEADSET: 601 mBluetoothHeadset = null; 602 break; 603 case BluetoothProfile.A2DP: 604 mA2dp = null; 605 mA2dpCodecConfig = null; 606 break; 607 case BluetoothProfile.HEARING_AID: 608 mHearingAid = null; 609 break; 610 case BluetoothProfile.LE_AUDIO: 611 if (mLeAudio != null && mLeAudioCallback != null) { 612 try { 613 mLeAudio.unregisterCallback(mLeAudioCallback); 614 } catch (Exception e) { 615 Log.e(TAG, "Exception while unregistering callback for LE audio", e); 616 } 617 } 618 mLeAudio = null; 619 mLeAudioCallback = null; 620 mLeAudioCodecConfig = null; 621 break; 622 case BluetoothProfile.LE_AUDIO_BROADCAST: 623 mLeAudioBroadcastCodec = AudioSystem.AUDIO_FORMAT_DEFAULT; 624 break; 625 case BluetoothProfile.A2DP_SINK: 626 // nothing to do in BtHelper 627 break; 628 default: 629 // Not a valid profile to disconnect 630 Log.e(TAG, "onBtProfileDisconnected: Not a valid profile to disconnect " 631 + BluetoothProfile.getProfileName(profile)); 632 break; 633 } 634 } 635 636 // BluetoothLeAudio callback used to update the list of addresses in the same group as a 637 // connected LE Audio device 638 class MyLeAudioCallback implements BluetoothLeAudio.Callback { 639 @Override onCodecConfigChanged(int groupId, @NonNull BluetoothLeAudioCodecStatus status)640 public void onCodecConfigChanged(int groupId, 641 @NonNull BluetoothLeAudioCodecStatus status) { 642 // Do nothing 643 } 644 645 @Override onGroupNodeAdded(@onNull BluetoothDevice device, int groupId)646 public void onGroupNodeAdded(@NonNull BluetoothDevice device, int groupId) { 647 mDeviceBroker.postUpdateLeAudioGroupAddresses(groupId); 648 } 649 650 @Override onGroupNodeRemoved(@onNull BluetoothDevice device, int groupId)651 public void onGroupNodeRemoved(@NonNull BluetoothDevice device, int groupId) { 652 mDeviceBroker.postUpdateLeAudioGroupAddresses(groupId); 653 } 654 @Override onGroupStatusChanged(int groupId, int groupStatus)655 public void onGroupStatusChanged(int groupId, int groupStatus) { 656 mDeviceBroker.postUpdateLeAudioGroupAddresses(groupId); 657 } 658 } 659 660 @GuardedBy("BtHelper.this") 661 MyLeAudioCallback mLeAudioCallback = null; 662 663 @GuardedBy("mDeviceBroker.mDeviceStateLock") onBtProfileConnected(int profile, BluetoothProfile proxy)664 /*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) { 665 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( 666 "BT profile " + BluetoothProfile.getProfileName(profile) + " connected to proxy " 667 + proxy).printLog(TAG)); 668 if (proxy == null) { 669 Log.e(TAG, "onBtProfileConnected: null proxy for profile: " + profile); 670 return; 671 } 672 switch (profile) { 673 case BluetoothProfile.HEADSET: 674 onHeadsetProfileConnected((BluetoothHeadset) proxy); 675 return; 676 case BluetoothProfile.A2DP: 677 if (((BluetoothA2dp) proxy).equals(mA2dp)) { 678 return; 679 } 680 mA2dp = (BluetoothA2dp) proxy; 681 break; 682 case BluetoothProfile.HEARING_AID: 683 if (((BluetoothHearingAid) proxy).equals(mHearingAid)) { 684 return; 685 } 686 mHearingAid = (BluetoothHearingAid) proxy; 687 break; 688 case BluetoothProfile.LE_AUDIO: 689 if (((BluetoothLeAudio) proxy).equals(mLeAudio)) { 690 return; 691 } 692 if (mLeAudio != null && mLeAudioCallback != null) { 693 try { 694 mLeAudio.unregisterCallback(mLeAudioCallback); 695 } catch (Exception e) { 696 Log.e(TAG, "Exception while unregistering callback for LE audio", e); 697 } 698 } 699 mLeAudio = (BluetoothLeAudio) proxy; 700 mLeAudioCallback = new MyLeAudioCallback(); 701 try{ 702 mLeAudio.registerCallback( 703 mContext.getMainExecutor(), mLeAudioCallback); 704 } catch (Exception e) { 705 mLeAudioCallback = null; 706 Log.e(TAG, "Exception while registering callback for LE audio", e); 707 } 708 break; 709 case BluetoothProfile.A2DP_SINK: 710 case BluetoothProfile.LE_AUDIO_BROADCAST: 711 // nothing to do in BtHelper 712 return; 713 default: 714 // Not a valid profile to connect 715 Log.e(TAG, "onBtProfileConnected: Not a valid profile to connect " 716 + BluetoothProfile.getProfileName(profile)); 717 return; 718 } 719 720 // this part is only for A2DP, LE Audio unicast and Hearing aid 721 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 722 if (adapter == null) { 723 Log.e(TAG, "onBtProfileConnected: Null BluetoothAdapter when connecting profile: " 724 + BluetoothProfile.getProfileName(profile)); 725 return; 726 } 727 List<BluetoothDevice> activeDevices = adapter.getActiveDevices(profile); 728 if (activeDevices.isEmpty() || activeDevices.get(0) == null) { 729 return; 730 } 731 BluetoothDevice device = activeDevices.get(0); 732 switch (profile) { 733 case BluetoothProfile.A2DP: { 734 BluetoothProfileConnectionInfo bpci = 735 BluetoothProfileConnectionInfo.createA2dpInfo(false, -1); 736 postBluetoothActiveDevice(device, bpci); 737 } break; 738 case BluetoothProfile.HEARING_AID: { 739 BluetoothProfileConnectionInfo bpci = 740 BluetoothProfileConnectionInfo.createHearingAidInfo(false); 741 postBluetoothActiveDevice(device, bpci); 742 } break; 743 case BluetoothProfile.LE_AUDIO: { 744 int groupId = mLeAudio.getGroupId(device); 745 BluetoothLeAudioCodecStatus btLeCodecStatus = null; 746 try { 747 btLeCodecStatus = mLeAudio.getCodecStatus(groupId); 748 } catch (Exception e) { 749 Log.e(TAG, "Exception while getting status of " + device, e); 750 } 751 if (btLeCodecStatus == null) { 752 Log.i(TAG, "onBtProfileConnected null LE codec status for groupId: " 753 + groupId + ", device: " + device); 754 break; 755 } 756 List<BluetoothLeAudioCodecConfig> outputCodecConfigs = 757 btLeCodecStatus.getOutputCodecSelectableCapabilities(); 758 if (!outputCodecConfigs.isEmpty()) { 759 BluetoothProfileConnectionInfo bpci = 760 BluetoothProfileConnectionInfo.createLeAudioInfo( 761 false /*suppressNoisyIntent*/, true /*isLeOutput*/); 762 postBluetoothActiveDevice(device, bpci); 763 } 764 List<BluetoothLeAudioCodecConfig> inputCodecConfigs = 765 btLeCodecStatus.getInputCodecSelectableCapabilities(); 766 if (!inputCodecConfigs.isEmpty()) { 767 BluetoothProfileConnectionInfo bpci = 768 BluetoothProfileConnectionInfo.createLeAudioInfo( 769 false /*suppressNoisyIntent*/, false /*isLeOutput*/); 770 postBluetoothActiveDevice(device, bpci); 771 } 772 } break; 773 default: 774 // Not a valid profile to connect 775 Log.wtf(TAG, "Invalid profile! onBtProfileConnected"); 776 break; 777 } 778 } 779 postBluetoothActiveDevice( BluetoothDevice device, BluetoothProfileConnectionInfo bpci)780 private void postBluetoothActiveDevice( 781 BluetoothDevice device, BluetoothProfileConnectionInfo bpci) { 782 AudioDeviceBroker.BtDeviceChangedData data = new AudioDeviceBroker.BtDeviceChangedData( 783 device, null, bpci, "mBluetoothProfileServiceListener"); 784 AudioDeviceBroker.BtDeviceInfo info = mDeviceBroker.createBtDeviceInfo( 785 data, device, BluetoothProfile.STATE_CONNECTED); 786 mDeviceBroker.postBluetoothActiveDevice(info, 0 /* delay */); 787 } 788 isProfilePoxyConnected(int profile)789 /*package*/ synchronized boolean isProfilePoxyConnected(int profile) { 790 switch (profile) { 791 case BluetoothProfile.HEADSET: 792 return mBluetoothHeadset != null; 793 case BluetoothProfile.A2DP: 794 return mA2dp != null; 795 case BluetoothProfile.HEARING_AID: 796 return mHearingAid != null; 797 case BluetoothProfile.LE_AUDIO: 798 return mLeAudio != null; 799 case BluetoothProfile.A2DP_SINK: 800 case BluetoothProfile.LE_AUDIO_BROADCAST: 801 default: 802 // return true for profiles that are not managed by the BtHelper because 803 // the fact that the profile proxy is not connected does not affect 804 // the device connection handling. 805 return true; 806 } 807 } 808 809 @GuardedBy("mDeviceBroker.mDeviceStateLock") onHeadsetProfileConnected(@onNull BluetoothHeadset headset)810 private synchronized void onHeadsetProfileConnected(@NonNull BluetoothHeadset headset) { 811 // Discard timeout message 812 mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService(); 813 mBluetoothHeadset = headset; 814 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 815 if (adapter != null) { 816 List<BluetoothDevice> activeDevices = 817 adapter.getActiveDevices(BluetoothProfile.HEADSET); 818 for (BluetoothDevice device : activeDevices) { 819 if (device == null) { 820 continue; 821 } 822 onSetBtScoActiveDevice(device, false /*deviceSwitch*/); 823 } 824 } else { 825 Log.e(TAG, "onHeadsetProfileConnected: Null BluetoothAdapter"); 826 } 827 828 // Refresh SCO audio state 829 checkScoAudioState(); 830 if (mScoAudioState != SCO_STATE_ACTIVATE_REQ 831 && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { 832 return; 833 } 834 boolean status = false; 835 if (mBluetoothHeadsetDevice != null) { 836 switch (mScoAudioState) { 837 case SCO_STATE_ACTIVATE_REQ: 838 status = connectBluetoothScoAudioHelper( 839 mBluetoothHeadset, 840 mBluetoothHeadsetDevice, mScoAudioMode); 841 if (status) { 842 mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; 843 } 844 break; 845 case SCO_STATE_DEACTIVATE_REQ: 846 status = disconnectBluetoothScoAudioHelper( 847 mBluetoothHeadset, 848 mBluetoothHeadsetDevice, mScoAudioMode); 849 if (status) { 850 mScoAudioState = SCO_STATE_DEACTIVATING; 851 } 852 break; 853 } 854 } 855 if (!status) { 856 mScoAudioState = SCO_STATE_INACTIVE; 857 broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); 858 } 859 } 860 861 //---------------------------------------------------------------------- broadcastScoConnectionState(int state)862 private void broadcastScoConnectionState(int state) { 863 mDeviceBroker.postBroadcastScoConnectionState(state); 864 } 865 866 @GuardedBy("mDeviceBroker.mDeviceStateLock") getHeadsetAudioDevice()867 @Nullable AudioDeviceAttributes getHeadsetAudioDevice() { 868 if (mBluetoothHeadsetDevice == null) { 869 return null; 870 } 871 return getHeadsetAudioDevice(mBluetoothHeadsetDevice); 872 } 873 874 @GuardedBy("mDeviceBroker.mDeviceStateLock") getHeadsetAudioDevice(BluetoothDevice btDevice)875 private @NonNull AudioDeviceAttributes getHeadsetAudioDevice(BluetoothDevice btDevice) { 876 AudioDeviceAttributes deviceAttr = mResolvedScoAudioDevices.get(btDevice); 877 if (deviceAttr != null) { 878 // Returns the cached device attributes so that it is consistent as the previous one. 879 return deviceAttr; 880 } 881 return btHeadsetDeviceToAudioDevice(btDevice); 882 } 883 btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice)884 private static AudioDeviceAttributes btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice) { 885 if (btDevice == null) { 886 return new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""); 887 } 888 String address = btDevice.getAddress(); 889 String name = getName(btDevice); 890 if (!BluetoothAdapter.checkBluetoothAddress(address)) { 891 address = ""; 892 } 893 BluetoothClass btClass = btDevice.getBluetoothClass(); 894 int nativeType = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO; 895 if (btClass != null) { 896 switch (btClass.getDeviceClass()) { 897 case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: 898 case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: 899 nativeType = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET; 900 break; 901 case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: 902 nativeType = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT; 903 break; 904 } 905 } 906 if (AudioService.DEBUG_DEVICES) { 907 Log.i(TAG, "btHeadsetDeviceToAudioDevice btDevice: " + btDevice 908 + " btClass: " + (btClass == null ? "Unknown" : btClass) 909 + " nativeType: " + nativeType + " address: " + address); 910 } 911 return new AudioDeviceAttributes(nativeType, address, name); 912 } 913 914 @GuardedBy("mDeviceBroker.mDeviceStateLock") handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive, boolean deviceSwitch)915 private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive, 916 boolean deviceSwitch) { 917 if (btDevice == null) { 918 return true; 919 } 920 boolean result = false; 921 AudioDeviceAttributes audioDevice = null; // Only used if isActive is true 922 String address = btDevice.getAddress(); 923 String name = getName(btDevice); 924 // Handle output device 925 if (isActive) { 926 audioDevice = btHeadsetDeviceToAudioDevice(btDevice); 927 result = mDeviceBroker.handleDeviceConnection( 928 audioDevice, true /*connect*/, btDevice, false /*deviceSwitch*/); 929 } else { 930 AudioDeviceAttributes ada = mResolvedScoAudioDevices.get(btDevice); 931 if (ada != null) { 932 result = mDeviceBroker.handleDeviceConnection( 933 ada, false /*connect*/, btDevice, deviceSwitch); 934 } else { 935 // Disconnect all possible audio device types if the disconnected device type is 936 // unknown 937 int[] outDeviceTypes = { 938 AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, 939 AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET, 940 AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT 941 }; 942 for (int outDeviceType : outDeviceTypes) { 943 result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( 944 outDeviceType, address, name), false /*connect*/, btDevice, 945 deviceSwitch); 946 } 947 } 948 } 949 // Handle input device 950 int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; 951 // handleDeviceConnection() && result to make sure the method get executed 952 result = mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( 953 inDevice, address, name), 954 isActive, btDevice, deviceSwitch) && result; 955 if (result) { 956 if (isActive) { 957 mResolvedScoAudioDevices.put(btDevice, audioDevice); 958 } else { 959 mResolvedScoAudioDevices.remove(btDevice); 960 } 961 } 962 return result; 963 } 964 965 // Return `(null)` if given BluetoothDevice is null. Otherwise, return the anonymized address. getAnonymizedAddress(BluetoothDevice btDevice)966 private String getAnonymizedAddress(BluetoothDevice btDevice) { 967 return btDevice == null ? "(null)" : btDevice.getAnonymizedAddress(); 968 } 969 970 @GuardedBy("mDeviceBroker.mDeviceStateLock") onSetBtScoActiveDevice(BluetoothDevice btDevice, boolean deviceSwitch)971 /*package */ void onSetBtScoActiveDevice(BluetoothDevice btDevice, boolean deviceSwitch) { 972 Log.i(TAG, "onSetBtScoActiveDevice: " + getAnonymizedAddress(mBluetoothHeadsetDevice) 973 + " -> " + getAnonymizedAddress(btDevice) + ", deviceSwitch: " + deviceSwitch); 974 final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice; 975 if (Objects.equals(btDevice, previousActiveDevice)) { 976 return; 977 } 978 if (!handleBtScoActiveDeviceChange(previousActiveDevice, false, deviceSwitch)) { 979 Log.w(TAG, "onSetBtScoActiveDevice() failed to remove previous device " 980 + getAnonymizedAddress(previousActiveDevice)); 981 } 982 if (!handleBtScoActiveDeviceChange(btDevice, true, false /*deviceSwitch*/)) { 983 Log.e(TAG, "onSetBtScoActiveDevice() failed to add new device " 984 + getAnonymizedAddress(btDevice)); 985 // set mBluetoothHeadsetDevice to null when failing to add new device 986 btDevice = null; 987 } 988 mBluetoothHeadsetDevice = btDevice; 989 if (mBluetoothHeadsetDevice == null) { 990 resetBluetoothSco(); 991 } 992 } 993 994 // NOTE this listener is NOT called from AudioDeviceBroker event thread, only call async 995 // methods inside listener. 996 private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = 997 new BluetoothProfile.ServiceListener() { 998 public void onServiceConnected(int profile, BluetoothProfile proxy) { 999 switch(profile) { 1000 case BluetoothProfile.A2DP: 1001 case BluetoothProfile.HEADSET: 1002 case BluetoothProfile.HEARING_AID: 1003 case BluetoothProfile.LE_AUDIO: 1004 case BluetoothProfile.A2DP_SINK: 1005 case BluetoothProfile.LE_AUDIO_BROADCAST: 1006 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( 1007 "BT profile service: connecting " 1008 + BluetoothProfile.getProfileName(profile) 1009 + " profile").printLog(TAG)); 1010 mDeviceBroker.postBtProfileConnected(profile, proxy); 1011 break; 1012 1013 default: 1014 break; 1015 } 1016 } 1017 public void onServiceDisconnected(int profile) { 1018 1019 switch (profile) { 1020 case BluetoothProfile.A2DP: 1021 case BluetoothProfile.HEADSET: 1022 case BluetoothProfile.HEARING_AID: 1023 case BluetoothProfile.LE_AUDIO: 1024 case BluetoothProfile.A2DP_SINK: 1025 case BluetoothProfile.LE_AUDIO_BROADCAST: 1026 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( 1027 "BT profile service: disconnecting " 1028 + BluetoothProfile.getProfileName(profile) 1029 + " profile").printLog(TAG)); 1030 mDeviceBroker.postBtProfileDisconnected(profile); 1031 break; 1032 1033 default: 1034 break; 1035 } 1036 } 1037 }; 1038 1039 //---------------------------------------------------------------------- 1040 1041 @GuardedBy("mDeviceBroker.mDeviceStateLock") requestScoState(int state, int scoAudioMode)1042 private synchronized boolean requestScoState(int state, int scoAudioMode) { 1043 checkScoAudioState(); 1044 if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { 1045 // Make sure that the state transitions to CONNECTING even if we cannot initiate 1046 // the connection except if already connected internally 1047 if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL) { 1048 broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING); 1049 } 1050 switch (mScoAudioState) { 1051 case SCO_STATE_INACTIVE: 1052 mScoAudioMode = scoAudioMode; 1053 if (scoAudioMode == SCO_MODE_UNDEFINED) { 1054 mScoAudioMode = SCO_MODE_VIRTUAL_CALL; 1055 if (mBluetoothHeadsetDevice != null) { 1056 mScoAudioMode = Settings.Global.getInt( 1057 mDeviceBroker.getContentResolver(), 1058 "bluetooth_sco_channel_" 1059 + mBluetoothHeadsetDevice.getAddress(), 1060 SCO_MODE_VIRTUAL_CALL); 1061 if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) { 1062 mScoAudioMode = SCO_MODE_VIRTUAL_CALL; 1063 } 1064 } 1065 } 1066 if (mBluetoothHeadset == null) { 1067 if (getBluetoothHeadset()) { 1068 mScoAudioState = SCO_STATE_ACTIVATE_REQ; 1069 } else { 1070 Log.w(TAG, "requestScoState: getBluetoothHeadset failed during" 1071 + " connection, mScoAudioMode=" + mScoAudioMode); 1072 broadcastScoConnectionState( 1073 AudioManager.SCO_AUDIO_STATE_DISCONNECTED); 1074 return false; 1075 } 1076 break; 1077 } 1078 if (mBluetoothHeadsetDevice == null) { 1079 Log.w(TAG, "requestScoState: no active device while connecting," 1080 + " mScoAudioMode=" + mScoAudioMode); 1081 broadcastScoConnectionState( 1082 AudioManager.SCO_AUDIO_STATE_DISCONNECTED); 1083 return false; 1084 } 1085 if (connectBluetoothScoAudioHelper(mBluetoothHeadset, 1086 mBluetoothHeadsetDevice, mScoAudioMode)) { 1087 mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; 1088 } else { 1089 Log.w(TAG, "requestScoState: connect to " 1090 + getAnonymizedAddress(mBluetoothHeadsetDevice) 1091 + " failed, mScoAudioMode=" + mScoAudioMode); 1092 broadcastScoConnectionState( 1093 AudioManager.SCO_AUDIO_STATE_DISCONNECTED); 1094 return false; 1095 } 1096 break; 1097 case SCO_STATE_DEACTIVATING: 1098 mScoAudioState = SCO_STATE_ACTIVATE_REQ; 1099 break; 1100 case SCO_STATE_DEACTIVATE_REQ: 1101 mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; 1102 broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED); 1103 break; 1104 case SCO_STATE_ACTIVE_INTERNAL: 1105 // Already in ACTIVE mode, simply return 1106 break; 1107 case SCO_STATE_ACTIVE_EXTERNAL: 1108 /* Confirm SCO Audio connection to requesting app as it is already connected 1109 * externally (i.e. through SCO APIs by Telecom service). 1110 * Once SCO Audio is disconnected by the external owner, we will reconnect it 1111 * automatically on behalf of the requesting app and the state will move to 1112 * SCO_STATE_ACTIVE_INTERNAL. 1113 */ 1114 broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED); 1115 break; 1116 default: 1117 Log.w(TAG, "requestScoState: failed to connect in state " 1118 + mScoAudioState + ", scoAudioMode=" + scoAudioMode); 1119 broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); 1120 return false; 1121 } 1122 } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { 1123 switch (mScoAudioState) { 1124 case SCO_STATE_ACTIVE_INTERNAL: 1125 if (mBluetoothHeadset == null) { 1126 if (getBluetoothHeadset()) { 1127 mScoAudioState = SCO_STATE_DEACTIVATE_REQ; 1128 } else { 1129 Log.w(TAG, "requestScoState: getBluetoothHeadset failed during" 1130 + " disconnection, mScoAudioMode=" + mScoAudioMode); 1131 mScoAudioState = SCO_STATE_INACTIVE; 1132 broadcastScoConnectionState( 1133 AudioManager.SCO_AUDIO_STATE_DISCONNECTED); 1134 return false; 1135 } 1136 break; 1137 } 1138 if (mBluetoothHeadsetDevice == null) { 1139 mScoAudioState = SCO_STATE_INACTIVE; 1140 broadcastScoConnectionState( 1141 AudioManager.SCO_AUDIO_STATE_DISCONNECTED); 1142 break; 1143 } 1144 if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset, 1145 mBluetoothHeadsetDevice, mScoAudioMode)) { 1146 mScoAudioState = SCO_STATE_DEACTIVATING; 1147 } else { 1148 mScoAudioState = SCO_STATE_INACTIVE; 1149 broadcastScoConnectionState( 1150 AudioManager.SCO_AUDIO_STATE_DISCONNECTED); 1151 } 1152 break; 1153 case SCO_STATE_ACTIVATE_REQ: 1154 mScoAudioState = SCO_STATE_INACTIVE; 1155 broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); 1156 break; 1157 default: 1158 Log.w(TAG, "requestScoState: failed to disconnect in state " 1159 + mScoAudioState + ", scoAudioMode=" + scoAudioMode); 1160 broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); 1161 return false; 1162 } 1163 } 1164 return true; 1165 } 1166 1167 //----------------------------------------------------- 1168 // Utilities 1169 1170 // suppress warning due to generic Intent passed as param 1171 @SuppressWarnings("AndroidFrameworkRequiresPermission") sendStickyBroadcastToAll(Intent intent)1172 private void sendStickyBroadcastToAll(Intent intent) { 1173 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 1174 final long ident = Binder.clearCallingIdentity(); 1175 try { 1176 mDeviceBroker.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL); 1177 } finally { 1178 Binder.restoreCallingIdentity(ident); 1179 } 1180 } 1181 disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, BluetoothDevice device, int scoAudioMode)1182 private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, 1183 BluetoothDevice device, int scoAudioMode) { 1184 switch (scoAudioMode) { 1185 case SCO_MODE_VIRTUAL_CALL: 1186 return bluetoothHeadset.stopScoUsingVirtualVoiceCall(); 1187 case SCO_MODE_VR: 1188 return bluetoothHeadset.stopVoiceRecognition(device); 1189 default: 1190 return false; 1191 } 1192 } 1193 connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, BluetoothDevice device, int scoAudioMode)1194 private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, 1195 BluetoothDevice device, int scoAudioMode) { 1196 switch (scoAudioMode) { 1197 case SCO_MODE_VIRTUAL_CALL: 1198 return bluetoothHeadset.startScoUsingVirtualVoiceCall(); 1199 case SCO_MODE_VR: 1200 return bluetoothHeadset.startVoiceRecognition(device); 1201 default: 1202 return false; 1203 } 1204 } 1205 1206 @GuardedBy("mDeviceBroker.mDeviceStateLock") checkScoAudioState()1207 private synchronized void checkScoAudioState() { 1208 try { 1209 if (mBluetoothHeadset != null 1210 && mBluetoothHeadsetDevice != null 1211 && mScoAudioState == SCO_STATE_INACTIVE 1212 && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) 1213 != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { 1214 mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; 1215 } 1216 } catch (Exception e) { 1217 Log.e(TAG, "Exception while getting audio state of " + mBluetoothHeadsetDevice, e); 1218 } 1219 } 1220 getBluetoothHeadset()1221 private boolean getBluetoothHeadset() { 1222 boolean result = false; 1223 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 1224 if (adapter != null) { 1225 result = adapter.getProfileProxy(mDeviceBroker.getContext(), 1226 mBluetoothProfileServiceListener, BluetoothProfile.HEADSET); 1227 } 1228 // If we could not get a bluetooth headset proxy, send a failure message 1229 // without delay to reset the SCO audio state and clear SCO clients. 1230 // If we could get a proxy, send a delayed failure message that will reset our state 1231 // in case we don't receive onServiceConnected(). 1232 mDeviceBroker.handleFailureToConnectToBtHeadsetService( 1233 result ? AudioDeviceBroker.BT_HEADSET_CNCT_TIMEOUT_MS : 0); 1234 return result; 1235 } 1236 getLeAudioDeviceGroupId(BluetoothDevice device)1237 /*package*/ synchronized int getLeAudioDeviceGroupId(BluetoothDevice device) { 1238 if (mLeAudio == null || device == null) { 1239 return BluetoothLeAudio.GROUP_ID_INVALID; 1240 } 1241 return mLeAudio.getGroupId(device); 1242 } 1243 1244 /** 1245 * Returns all addresses and identity addresses for LE Audio devices a group. 1246 * @param groupId The ID of the group from which to get addresses. 1247 * @return A List of Pair(String main_address, String identity_address). Note that the 1248 * addresses returned by BluetoothDevice can be null. 1249 */ getLeAudioGroupAddresses(int groupId)1250 /*package*/ synchronized List<Pair<String, String>> getLeAudioGroupAddresses(int groupId) { 1251 List<Pair<String, String>> addresses = new ArrayList<>(); 1252 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 1253 if (adapter == null || mLeAudio == null) { 1254 return addresses; 1255 } 1256 List<BluetoothDevice> activeDevices = adapter.getActiveDevices(BluetoothProfile.LE_AUDIO); 1257 for (BluetoothDevice device : activeDevices) { 1258 if (device != null && mLeAudio.getGroupId(device) == groupId) { 1259 addresses.add(new Pair(device.getAddress(), device.getIdentityAddress())); 1260 } 1261 } 1262 return addresses; 1263 } 1264 1265 /** 1266 * Returns the String equivalent of the btCodecType. 1267 * 1268 * This uses an "ENCODING_" prefix for consistency with Audio; 1269 * we could alternately use the "SOURCE_CODEC_TYPE_" prefix from Bluetooth. 1270 */ bluetoothCodecToEncodingString(int btCodecType)1271 public static String bluetoothCodecToEncodingString(int btCodecType) { 1272 switch (btCodecType) { 1273 case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC: 1274 return "ENCODING_SBC"; 1275 case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC: 1276 return "ENCODING_AAC"; 1277 case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX: 1278 return "ENCODING_APTX"; 1279 case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD: 1280 return "ENCODING_APTX_HD"; 1281 case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC: 1282 return "ENCODING_LDAC"; 1283 case BluetoothCodecConfig.SOURCE_CODEC_TYPE_OPUS: 1284 return "ENCODING_OPUS"; 1285 default: 1286 return "ENCODING_BT_CODEC_TYPE(" + btCodecType + ")"; 1287 } 1288 } 1289 getProfileFromType(int deviceType)1290 /*package */ static int getProfileFromType(int deviceType) { 1291 if (AudioSystem.isBluetoothA2dpOutDevice(deviceType)) { 1292 return BluetoothProfile.A2DP; 1293 } else if (AudioSystem.isBluetoothScoDevice(deviceType)) { 1294 return BluetoothProfile.HEADSET; 1295 } else if (AudioSystem.isBluetoothLeDevice(deviceType)) { 1296 return BluetoothProfile.LE_AUDIO; 1297 } 1298 return 0; // 0 is not a valid profile 1299 } 1300 getTypeFromProfile(int profile, boolean isLeOutput)1301 /*package */ static int getTypeFromProfile(int profile, boolean isLeOutput) { 1302 switch (profile) { 1303 case BluetoothProfile.A2DP_SINK: 1304 return AudioSystem.DEVICE_IN_BLUETOOTH_A2DP; 1305 case BluetoothProfile.A2DP: 1306 return AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP; 1307 case BluetoothProfile.HEARING_AID: 1308 return AudioSystem.DEVICE_OUT_HEARING_AID; 1309 case BluetoothProfile.LE_AUDIO: 1310 if (isLeOutput) { 1311 return AudioSystem.DEVICE_OUT_BLE_HEADSET; 1312 } else { 1313 return AudioSystem.DEVICE_IN_BLE_HEADSET; 1314 } 1315 case BluetoothProfile.LE_AUDIO_BROADCAST: 1316 return AudioSystem.DEVICE_OUT_BLE_BROADCAST; 1317 case BluetoothProfile.HEADSET: 1318 return AudioSystem.DEVICE_OUT_BLUETOOTH_SCO; 1319 default: 1320 throw new IllegalArgumentException("Invalid profile " + profile); 1321 } 1322 } 1323 getPreferredAudioProfiles(String address)1324 /*package */ static Bundle getPreferredAudioProfiles(String address) { 1325 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 1326 return adapter.getPreferredAudioProfiles(adapter.getRemoteDevice(address)); 1327 } 1328 1329 @Nullable getBluetoothDevice(String address)1330 /*package */ static BluetoothDevice getBluetoothDevice(String address) { 1331 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 1332 if (adapter == null || !BluetoothAdapter.checkBluetoothAddress(address)) { 1333 return null; 1334 } 1335 1336 return adapter.getRemoteDevice(address); 1337 } 1338 1339 @AudioDeviceCategory getBtDeviceCategory(String address)1340 /*package*/ static int getBtDeviceCategory(String address) { 1341 BluetoothDevice device = BtHelper.getBluetoothDevice(address); 1342 if (device == null) { 1343 return AUDIO_DEVICE_CATEGORY_UNKNOWN; 1344 } 1345 1346 byte[] deviceType = device.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE); 1347 if (deviceType == null) { 1348 return AUDIO_DEVICE_CATEGORY_UNKNOWN; 1349 } 1350 String deviceCategory = new String(deviceType); 1351 switch (deviceCategory) { 1352 case DEVICE_TYPE_HEARING_AID: 1353 return AUDIO_DEVICE_CATEGORY_HEARING_AID; 1354 case DEVICE_TYPE_CARKIT: 1355 return AUDIO_DEVICE_CATEGORY_CARKIT; 1356 case DEVICE_TYPE_HEADSET: 1357 case DEVICE_TYPE_UNTETHERED_HEADSET: 1358 return AUDIO_DEVICE_CATEGORY_HEADPHONES; 1359 case DEVICE_TYPE_SPEAKER: 1360 return AUDIO_DEVICE_CATEGORY_SPEAKER; 1361 case DEVICE_TYPE_WATCH: 1362 return AUDIO_DEVICE_CATEGORY_WATCH; 1363 case DEVICE_TYPE_DEFAULT: 1364 default: 1365 // fall through 1366 } 1367 1368 BluetoothClass deviceClass = device.getBluetoothClass(); 1369 if (deviceClass == null) { 1370 return AUDIO_DEVICE_CATEGORY_UNKNOWN; 1371 } 1372 1373 switch (deviceClass.getDeviceClass()) { 1374 case BluetoothClass.Device.WEARABLE_WRIST_WATCH: 1375 return AUDIO_DEVICE_CATEGORY_WATCH; 1376 case BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER: 1377 case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER: 1378 case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO: 1379 return AUDIO_DEVICE_CATEGORY_SPEAKER; 1380 case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: 1381 case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES: 1382 return AUDIO_DEVICE_CATEGORY_HEADPHONES; 1383 case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO: 1384 return AUDIO_DEVICE_CATEGORY_RECEIVER; 1385 default: 1386 return AUDIO_DEVICE_CATEGORY_UNKNOWN; 1387 } 1388 } 1389 1390 /** 1391 * Notifies Bluetooth framework that new preferred audio profiles for Bluetooth devices 1392 * have been applied. 1393 */ onNotifyPreferredAudioProfileApplied(BluetoothDevice btDevice)1394 public static void onNotifyPreferredAudioProfileApplied(BluetoothDevice btDevice) { 1395 BluetoothAdapter.getDefaultAdapter().notifyActiveDeviceChangeApplied(btDevice); 1396 } 1397 1398 /** 1399 * Returns the string equivalent for the btDeviceClass class. 1400 */ btDeviceClassToString(int btDeviceClass)1401 public static String btDeviceClassToString(int btDeviceClass) { 1402 switch (btDeviceClass) { 1403 case BluetoothClass.Device.AUDIO_VIDEO_UNCATEGORIZED: 1404 return "AUDIO_VIDEO_UNCATEGORIZED"; 1405 case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: 1406 return "AUDIO_VIDEO_WEARABLE_HEADSET"; 1407 case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: 1408 return "AUDIO_VIDEO_HANDSFREE"; 1409 case 0x040C: 1410 return "AUDIO_VIDEO_RESERVED_0x040C"; // uncommon 1411 case BluetoothClass.Device.AUDIO_VIDEO_MICROPHONE: 1412 return "AUDIO_VIDEO_MICROPHONE"; 1413 case BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER: 1414 return "AUDIO_VIDEO_LOUDSPEAKER"; 1415 case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES: 1416 return "AUDIO_VIDEO_HEADPHONES"; 1417 case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO: 1418 return "AUDIO_VIDEO_PORTABLE_AUDIO"; 1419 case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: 1420 return "AUDIO_VIDEO_CAR_AUDIO"; 1421 case BluetoothClass.Device.AUDIO_VIDEO_SET_TOP_BOX: 1422 return "AUDIO_VIDEO_SET_TOP_BOX"; 1423 case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO: 1424 return "AUDIO_VIDEO_HIFI_AUDIO"; 1425 case BluetoothClass.Device.AUDIO_VIDEO_VCR: 1426 return "AUDIO_VIDEO_VCR"; 1427 case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_CAMERA: 1428 return "AUDIO_VIDEO_VIDEO_CAMERA"; 1429 case BluetoothClass.Device.AUDIO_VIDEO_CAMCORDER: 1430 return "AUDIO_VIDEO_CAMCORDER"; 1431 case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_MONITOR: 1432 return "AUDIO_VIDEO_VIDEO_MONITOR"; 1433 case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER: 1434 return "AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER"; 1435 case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_CONFERENCING: 1436 return "AUDIO_VIDEO_VIDEO_CONFERENCING"; 1437 case 0x0444: 1438 return "AUDIO_VIDEO_RESERVED_0x0444"; // uncommon 1439 case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_GAMING_TOY: 1440 return "AUDIO_VIDEO_VIDEO_GAMING_TOY"; 1441 default: // other device classes printed as a hex string. 1442 return TextUtils.formatSimple("0x%04x", btDeviceClass); 1443 } 1444 } 1445 1446 //------------------------------------------------------------ dump(PrintWriter pw, String prefix)1447 /*package*/ void dump(PrintWriter pw, String prefix) { 1448 pw.println("\n" + prefix + "mBluetoothHeadset: " + mBluetoothHeadset); 1449 pw.println(prefix + "mBluetoothHeadsetDevice: " + mBluetoothHeadsetDevice); 1450 if (mBluetoothHeadsetDevice != null) { 1451 final BluetoothClass bluetoothClass = mBluetoothHeadsetDevice.getBluetoothClass(); 1452 if (bluetoothClass != null) { 1453 pw.println(prefix + "mBluetoothHeadsetDevice.DeviceClass: " 1454 + btDeviceClassToString(bluetoothClass.getDeviceClass())); 1455 } 1456 } 1457 pw.println(prefix + "mScoAudioState: " + scoAudioStateToString(mScoAudioState)); 1458 pw.println(prefix + "mScoAudioMode: " + scoAudioModeToString(mScoAudioMode)); 1459 pw.println("\n" + prefix + "mHearingAid: " + mHearingAid); 1460 pw.println("\n" + prefix + "mLeAudio: " + mLeAudio); 1461 pw.println(prefix + "mA2dp: " + mA2dp); 1462 pw.println(prefix + "mAvrcpAbsVolSupported: " + mAvrcpAbsVolSupported); 1463 } 1464 1465 } 1466