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