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