1 /* 2 * Copyright (C) 2012 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 17 package com.android.bluetooth.a2dp; 18 19 import android.bluetooth.BluetoothA2dp; 20 import android.bluetooth.BluetoothCodecConfig; 21 import android.bluetooth.BluetoothCodecStatus; 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothProfile; 24 import android.bluetooth.BluetoothUuid; 25 import android.bluetooth.IBluetoothA2dp; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.media.AudioManager; 31 import android.os.HandlerThread; 32 import android.util.Log; 33 import android.util.StatsLog; 34 35 import com.android.bluetooth.BluetoothMetricsProto; 36 import com.android.bluetooth.Utils; 37 import com.android.bluetooth.btservice.AdapterService; 38 import com.android.bluetooth.btservice.MetricsLogger; 39 import com.android.bluetooth.btservice.ProfileService; 40 import com.android.bluetooth.btservice.ServiceFactory; 41 import com.android.internal.annotations.GuardedBy; 42 import com.android.internal.annotations.VisibleForTesting; 43 44 import java.util.ArrayList; 45 import java.util.List; 46 import java.util.Objects; 47 import java.util.concurrent.ConcurrentHashMap; 48 import java.util.concurrent.ConcurrentMap; 49 50 /** 51 * Provides Bluetooth A2DP profile, as a service in the Bluetooth application. 52 * @hide 53 */ 54 public class A2dpService extends ProfileService { 55 private static final boolean DBG = true; 56 private static final String TAG = "A2dpService"; 57 58 private static A2dpService sA2dpService; 59 60 private AdapterService mAdapterService; 61 private HandlerThread mStateMachinesThread; 62 63 @VisibleForTesting 64 A2dpNativeInterface mA2dpNativeInterface; 65 @VisibleForTesting 66 ServiceFactory mFactory = new ServiceFactory(); 67 private AudioManager mAudioManager; 68 private A2dpCodecConfig mA2dpCodecConfig; 69 70 @GuardedBy("mStateMachines") 71 private BluetoothDevice mActiveDevice; 72 private final ConcurrentMap<BluetoothDevice, A2dpStateMachine> mStateMachines = 73 new ConcurrentHashMap<>(); 74 75 // Upper limit of all A2DP devices: Bonded or Connected 76 private static final int MAX_A2DP_STATE_MACHINES = 50; 77 // Upper limit of all A2DP devices that are Connected or Connecting 78 private int mMaxConnectedAudioDevices = 1; 79 // A2DP Offload Enabled in platform 80 boolean mA2dpOffloadEnabled = false; 81 82 private BroadcastReceiver mBondStateChangedReceiver; 83 private BroadcastReceiver mConnectionStateChangedReceiver; 84 85 @Override initBinder()86 protected IProfileServiceBinder initBinder() { 87 return new BluetoothA2dpBinder(this); 88 } 89 90 @Override create()91 protected void create() { 92 Log.i(TAG, "create()"); 93 } 94 95 @Override start()96 protected boolean start() { 97 Log.i(TAG, "start()"); 98 if (sA2dpService != null) { 99 throw new IllegalStateException("start() called twice"); 100 } 101 102 // Step 1: Get AdapterService, A2dpNativeInterface, AudioManager. 103 // None of them can be null. 104 mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), 105 "AdapterService cannot be null when A2dpService starts"); 106 mA2dpNativeInterface = Objects.requireNonNull(A2dpNativeInterface.getInstance(), 107 "A2dpNativeInterface cannot be null when A2dpService starts"); 108 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 109 Objects.requireNonNull(mAudioManager, 110 "AudioManager cannot be null when A2dpService starts"); 111 112 // Step 2: Get maximum number of connected audio devices 113 mMaxConnectedAudioDevices = mAdapterService.getMaxConnectedAudioDevices(); 114 Log.i(TAG, "Max connected audio devices set to " + mMaxConnectedAudioDevices); 115 116 // Step 3: Start handler thread for state machines 117 mStateMachines.clear(); 118 mStateMachinesThread = new HandlerThread("A2dpService.StateMachines"); 119 mStateMachinesThread.start(); 120 121 // Step 4: Setup codec config 122 mA2dpCodecConfig = new A2dpCodecConfig(this, mA2dpNativeInterface); 123 124 // Step 5: Initialize native interface 125 mA2dpNativeInterface.init(mMaxConnectedAudioDevices, 126 mA2dpCodecConfig.codecConfigPriorities()); 127 128 // Step 6: Check if A2DP is in offload mode 129 mA2dpOffloadEnabled = mAdapterService.isA2dpOffloadEnabled(); 130 if (DBG) { 131 Log.d(TAG, "A2DP offload flag set to " + mA2dpOffloadEnabled); 132 } 133 134 // Step 7: Setup broadcast receivers 135 IntentFilter filter = new IntentFilter(); 136 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 137 mBondStateChangedReceiver = new BondStateChangedReceiver(); 138 registerReceiver(mBondStateChangedReceiver, filter); 139 filter = new IntentFilter(); 140 filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 141 mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver(); 142 registerReceiver(mConnectionStateChangedReceiver, filter); 143 144 // Step 8: Mark service as started 145 setA2dpService(this); 146 147 // Step 9: Clear active device 148 setActiveDevice(null); 149 150 return true; 151 } 152 153 @Override stop()154 protected boolean stop() { 155 Log.i(TAG, "stop()"); 156 if (sA2dpService == null) { 157 Log.w(TAG, "stop() called before start()"); 158 return true; 159 } 160 161 // Step 9: Clear active device and stop playing audio 162 removeActiveDevice(true); 163 164 // Step 8: Mark service as stopped 165 setA2dpService(null); 166 167 // Step 7: Unregister broadcast receivers 168 unregisterReceiver(mConnectionStateChangedReceiver); 169 mConnectionStateChangedReceiver = null; 170 unregisterReceiver(mBondStateChangedReceiver); 171 mBondStateChangedReceiver = null; 172 173 // Step 6: Cleanup native interface 174 mA2dpNativeInterface.cleanup(); 175 mA2dpNativeInterface = null; 176 177 // Step 5: Clear codec config 178 mA2dpCodecConfig = null; 179 180 // Step 4: Destroy state machines and stop handler thread 181 synchronized (mStateMachines) { 182 for (A2dpStateMachine sm : mStateMachines.values()) { 183 sm.doQuit(); 184 sm.cleanup(); 185 } 186 mStateMachines.clear(); 187 } 188 mStateMachinesThread.quitSafely(); 189 mStateMachinesThread = null; 190 191 // Step 2: Reset maximum number of connected audio devices 192 mMaxConnectedAudioDevices = 1; 193 194 // Step 1: Clear AdapterService, A2dpNativeInterface, AudioManager 195 mAudioManager = null; 196 mA2dpNativeInterface = null; 197 mAdapterService = null; 198 199 return true; 200 } 201 202 @Override cleanup()203 protected void cleanup() { 204 Log.i(TAG, "cleanup()"); 205 } 206 getA2dpService()207 public static synchronized A2dpService getA2dpService() { 208 if (sA2dpService == null) { 209 Log.w(TAG, "getA2dpService(): service is null"); 210 return null; 211 } 212 if (!sA2dpService.isAvailable()) { 213 Log.w(TAG, "getA2dpService(): service is not available"); 214 return null; 215 } 216 return sA2dpService; 217 } 218 setA2dpService(A2dpService instance)219 private static synchronized void setA2dpService(A2dpService instance) { 220 if (DBG) { 221 Log.d(TAG, "setA2dpService(): set to: " + instance); 222 } 223 sA2dpService = instance; 224 } 225 connect(BluetoothDevice device)226 public boolean connect(BluetoothDevice device) { 227 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); 228 if (DBG) { 229 Log.d(TAG, "connect(): " + device); 230 } 231 232 if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) { 233 Log.e(TAG, "Cannot connect to " + device + " : PRIORITY_OFF"); 234 return false; 235 } 236 if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device), 237 BluetoothUuid.AudioSink)) { 238 Log.e(TAG, "Cannot connect to " + device + " : Remote does not have A2DP Sink UUID"); 239 return false; 240 } 241 242 synchronized (mStateMachines) { 243 if (!connectionAllowedCheckMaxDevices(device)) { 244 // when mMaxConnectedAudioDevices is one, disconnect current device first. 245 if (mMaxConnectedAudioDevices == 1) { 246 List<BluetoothDevice> sinks = getDevicesMatchingConnectionStates( 247 new int[] {BluetoothProfile.STATE_CONNECTED, 248 BluetoothProfile.STATE_CONNECTING, 249 BluetoothProfile.STATE_DISCONNECTING}); 250 for (BluetoothDevice sink : sinks) { 251 if (sink.equals(device)) { 252 Log.w(TAG, "Connecting to device " + device + " : disconnect skipped"); 253 continue; 254 } 255 disconnect(sink); 256 } 257 } else { 258 Log.e(TAG, "Cannot connect to " + device + " : too many connected devices"); 259 return false; 260 } 261 } 262 A2dpStateMachine smConnect = getOrCreateStateMachine(device); 263 if (smConnect == null) { 264 Log.e(TAG, "Cannot connect to " + device + " : no state machine"); 265 return false; 266 } 267 smConnect.sendMessage(A2dpStateMachine.CONNECT); 268 return true; 269 } 270 } 271 disconnect(BluetoothDevice device)272 boolean disconnect(BluetoothDevice device) { 273 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); 274 if (DBG) { 275 Log.d(TAG, "disconnect(): " + device); 276 } 277 278 synchronized (mStateMachines) { 279 A2dpStateMachine sm = mStateMachines.get(device); 280 if (sm == null) { 281 Log.e(TAG, "Ignored disconnect request for " + device + " : no state machine"); 282 return false; 283 } 284 sm.sendMessage(A2dpStateMachine.DISCONNECT); 285 return true; 286 } 287 } 288 getConnectedDevices()289 public List<BluetoothDevice> getConnectedDevices() { 290 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 291 synchronized (mStateMachines) { 292 List<BluetoothDevice> devices = new ArrayList<>(); 293 for (A2dpStateMachine sm : mStateMachines.values()) { 294 if (sm.isConnected()) { 295 devices.add(sm.getDevice()); 296 } 297 } 298 return devices; 299 } 300 } 301 302 /** 303 * Check whether can connect to a peer device. 304 * The check considers the maximum number of connected peers. 305 * 306 * @param device the peer device to connect to 307 * @return true if connection is allowed, otherwise false 308 */ connectionAllowedCheckMaxDevices(BluetoothDevice device)309 private boolean connectionAllowedCheckMaxDevices(BluetoothDevice device) { 310 int connected = 0; 311 // Count devices that are in the process of connecting or already connected 312 synchronized (mStateMachines) { 313 for (A2dpStateMachine sm : mStateMachines.values()) { 314 switch (sm.getConnectionState()) { 315 case BluetoothProfile.STATE_CONNECTING: 316 case BluetoothProfile.STATE_CONNECTED: 317 if (Objects.equals(device, sm.getDevice())) { 318 return true; // Already connected or accounted for 319 } 320 connected++; 321 break; 322 default: 323 break; 324 } 325 } 326 } 327 return (connected < mMaxConnectedAudioDevices); 328 } 329 330 /** 331 * Check whether can connect to a peer device. 332 * The check considers a number of factors during the evaluation. 333 * 334 * @param device the peer device to connect to 335 * @param isOutgoingRequest if true, the check is for outgoing connection 336 * request, otherwise is for incoming connection request 337 * @return true if connection is allowed, otherwise false 338 */ 339 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) okToConnect(BluetoothDevice device, boolean isOutgoingRequest)340 public boolean okToConnect(BluetoothDevice device, boolean isOutgoingRequest) { 341 Log.i(TAG, "okToConnect: device " + device + " isOutgoingRequest: " + isOutgoingRequest); 342 // Check if this is an incoming connection in Quiet mode. 343 if (mAdapterService.isQuietModeEnabled() && !isOutgoingRequest) { 344 Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled"); 345 return false; 346 } 347 // Check if too many devices 348 if (!connectionAllowedCheckMaxDevices(device)) { 349 Log.e(TAG, "okToConnect: cannot connect to " + device 350 + " : too many connected devices"); 351 return false; 352 } 353 // Check priority and accept or reject the connection. 354 int priority = getPriority(device); 355 int bondState = mAdapterService.getBondState(device); 356 // Allow this connection only if the device is bonded. Any attempt to connect while 357 // bonding would potentially lead to an unauthorized connection. 358 if (bondState != BluetoothDevice.BOND_BONDED) { 359 Log.w(TAG, "okToConnect: return false, bondState=" + bondState); 360 return false; 361 } else if (priority != BluetoothProfile.PRIORITY_UNDEFINED 362 && priority != BluetoothProfile.PRIORITY_ON 363 && priority != BluetoothProfile.PRIORITY_AUTO_CONNECT) { 364 // Otherwise, reject the connection if priority is not valid. 365 Log.w(TAG, "okToConnect: return false, priority=" + priority); 366 return false; 367 } 368 return true; 369 } 370 getDevicesMatchingConnectionStates(int[] states)371 List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 372 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 373 List<BluetoothDevice> devices = new ArrayList<>(); 374 if (states == null) { 375 return devices; 376 } 377 final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); 378 if (bondedDevices == null) { 379 return devices; 380 } 381 synchronized (mStateMachines) { 382 for (BluetoothDevice device : bondedDevices) { 383 if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device), 384 BluetoothUuid.AudioSink)) { 385 continue; 386 } 387 int connectionState = BluetoothProfile.STATE_DISCONNECTED; 388 A2dpStateMachine sm = mStateMachines.get(device); 389 if (sm != null) { 390 connectionState = sm.getConnectionState(); 391 } 392 for (int state : states) { 393 if (connectionState == state) { 394 devices.add(device); 395 break; 396 } 397 } 398 } 399 return devices; 400 } 401 } 402 403 /** 404 * Get the list of devices that have state machines. 405 * 406 * @return the list of devices that have state machines 407 */ 408 @VisibleForTesting getDevices()409 List<BluetoothDevice> getDevices() { 410 List<BluetoothDevice> devices = new ArrayList<>(); 411 synchronized (mStateMachines) { 412 for (A2dpStateMachine sm : mStateMachines.values()) { 413 devices.add(sm.getDevice()); 414 } 415 return devices; 416 } 417 } 418 getConnectionState(BluetoothDevice device)419 public int getConnectionState(BluetoothDevice device) { 420 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 421 synchronized (mStateMachines) { 422 A2dpStateMachine sm = mStateMachines.get(device); 423 if (sm == null) { 424 return BluetoothProfile.STATE_DISCONNECTED; 425 } 426 return sm.getConnectionState(); 427 } 428 } 429 storeActiveDeviceVolume()430 private void storeActiveDeviceVolume() { 431 // Make sure volume has been stored before been removed from active. 432 if (mFactory.getAvrcpTargetService() != null && mActiveDevice != null) { 433 mFactory.getAvrcpTargetService().storeVolumeForDevice(mActiveDevice); 434 } 435 } 436 removeActiveDevice(boolean forceStopPlayingAudio)437 private void removeActiveDevice(boolean forceStopPlayingAudio) { 438 BluetoothDevice previousActiveDevice = mActiveDevice; 439 synchronized (mStateMachines) { 440 // Make sure volume has been store before device been remove from active. 441 storeActiveDeviceVolume(); 442 443 // This needs to happen before we inform the audio manager that the device 444 // disconnected. Please see comment in updateAndBroadcastActiveDevice() for why. 445 updateAndBroadcastActiveDevice(null); 446 447 if (previousActiveDevice == null) { 448 return; 449 } 450 451 // Make sure the Audio Manager knows the previous Active device is disconnected. 452 // However, if A2DP is still connected and not forcing stop audio for that remote 453 // device, the user has explicitly switched the output to the local device and music 454 // should continue playing. Otherwise, the remote device has been indeed disconnected 455 // and audio should be suspended before switching the output to the local device. 456 boolean suppressNoisyIntent = !forceStopPlayingAudio 457 && (getConnectionState(previousActiveDevice) 458 == BluetoothProfile.STATE_CONNECTED); 459 Log.i(TAG, "removeActiveDevice: suppressNoisyIntent=" + suppressNoisyIntent); 460 mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( 461 previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED, 462 BluetoothProfile.A2DP, suppressNoisyIntent, -1); 463 // Make sure the Active device in native layer is set to null and audio is off 464 if (!mA2dpNativeInterface.setActiveDevice(null)) { 465 Log.w(TAG, "setActiveDevice(null): Cannot remove active device in native " 466 + "layer"); 467 } 468 } 469 } 470 471 /** 472 * Process a change in the silence mode for a {@link BluetoothDevice}. 473 * 474 * @param device the device to change silence mode 475 * @param silence true to enable silence mode, false to disable. 476 * @return true on success, false on error 477 */ 478 @VisibleForTesting setSilenceMode(BluetoothDevice device, boolean silence)479 public boolean setSilenceMode(BluetoothDevice device, boolean silence) { 480 if (DBG) { 481 Log.d(TAG, "setSilenceMode(" + device + "): " + silence); 482 } 483 if (silence && Objects.equals(mActiveDevice, device)) { 484 removeActiveDevice(true); 485 } else if (!silence && mActiveDevice == null) { 486 // Set the device as the active device if currently no active device. 487 setActiveDevice(device); 488 } 489 if (!mA2dpNativeInterface.setSilenceDevice(device, silence)) { 490 Log.e(TAG, "Cannot set " + device + " silence mode " + silence + " in native layer"); 491 return false; 492 } 493 return true; 494 } 495 496 /** 497 * Early notification that Hearing Aids will be the active device. This allows the A2DP to save 498 * its volume before the Audio Service starts changing its media stream. 499 */ earlyNotifyHearingAidActive()500 public void earlyNotifyHearingAidActive() { 501 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); 502 503 synchronized (mStateMachines) { 504 // Switch active device from A2DP to Hearing Aids. 505 if (DBG) { 506 Log.d(TAG, "earlyNotifyHearingAidActive: Save volume for " + mActiveDevice); 507 } 508 storeActiveDeviceVolume(); 509 } 510 } 511 512 /** 513 * Set the active device. 514 * 515 * @param device the active device 516 * @return true on success, otherwise false 517 */ setActiveDevice(BluetoothDevice device)518 public boolean setActiveDevice(BluetoothDevice device) { 519 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); 520 synchronized (mStateMachines) { 521 BluetoothDevice previousActiveDevice = mActiveDevice; 522 if (DBG) { 523 Log.d(TAG, "setActiveDevice(" + device + "): previous is " + previousActiveDevice); 524 } 525 526 if (device == null) { 527 // Remove active device and continue playing audio only if necessary. 528 removeActiveDevice(false); 529 return true; 530 } 531 532 BluetoothCodecStatus codecStatus = null; 533 A2dpStateMachine sm = mStateMachines.get(device); 534 if (sm == null) { 535 Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active: " 536 + "no state machine"); 537 return false; 538 } 539 if (sm.getConnectionState() != BluetoothProfile.STATE_CONNECTED) { 540 Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active: " 541 + "device is not connected"); 542 return false; 543 } 544 if (!mA2dpNativeInterface.setActiveDevice(device)) { 545 Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active in native layer"); 546 return false; 547 } 548 codecStatus = sm.getCodecStatus(); 549 550 boolean deviceChanged = !Objects.equals(device, mActiveDevice); 551 if (deviceChanged) { 552 // Switch from one A2DP to another A2DP device 553 if (DBG) { 554 Log.d(TAG, "Switch A2DP devices to " + device + " from " + mActiveDevice); 555 } 556 storeActiveDeviceVolume(); 557 } 558 559 // This needs to happen before we inform the audio manager that the device 560 // disconnected. Please see comment in updateAndBroadcastActiveDevice() for why. 561 updateAndBroadcastActiveDevice(device); 562 if (deviceChanged) { 563 // Send an intent with the active device codec config 564 if (codecStatus != null) { 565 broadcastCodecConfig(mActiveDevice, codecStatus); 566 } 567 // Make sure the Audio Manager knows the previous Active device is disconnected, 568 // and the new Active device is connected. 569 // Also, mute and unmute the output during the switch to avoid audio glitches. 570 boolean wasMuted = false; 571 if (previousActiveDevice != null) { 572 if (!mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)) { 573 mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, 574 AudioManager.ADJUST_MUTE, AudioManager.FLAG_BLUETOOTH_ABS_VOLUME); 575 wasMuted = true; 576 } 577 mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( 578 previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED, 579 BluetoothProfile.A2DP, true, -1); 580 } 581 582 int rememberedVolume = -1; 583 if (mFactory.getAvrcpTargetService() != null) { 584 rememberedVolume = mFactory.getAvrcpTargetService() 585 .getRememberedVolumeForDevice(mActiveDevice); 586 } 587 588 mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( 589 mActiveDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, 590 true, rememberedVolume); 591 592 // Inform the Audio Service about the codec configuration 593 // change, so the Audio Service can reset accordingly the audio 594 // feeding parameters in the Audio HAL to the Bluetooth stack. 595 mAudioManager.handleBluetoothA2dpDeviceConfigChange(mActiveDevice); 596 if (wasMuted) { 597 mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, 598 AudioManager.ADJUST_UNMUTE, AudioManager.FLAG_BLUETOOTH_ABS_VOLUME); 599 } 600 } 601 } 602 return true; 603 } 604 605 /** 606 * Get the active device. 607 * 608 * @return the active device or null if no device is active 609 */ getActiveDevice()610 public BluetoothDevice getActiveDevice() { 611 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 612 synchronized (mStateMachines) { 613 return mActiveDevice; 614 } 615 } 616 isActiveDevice(BluetoothDevice device)617 private boolean isActiveDevice(BluetoothDevice device) { 618 synchronized (mStateMachines) { 619 return (device != null) && Objects.equals(device, mActiveDevice); 620 } 621 } 622 setPriority(BluetoothDevice device, int priority)623 public boolean setPriority(BluetoothDevice device, int priority) { 624 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 625 if (DBG) { 626 Log.d(TAG, "Saved priority " + device + " = " + priority); 627 } 628 mAdapterService.getDatabase() 629 .setProfilePriority(device, BluetoothProfile.A2DP, priority); 630 return true; 631 } 632 getPriority(BluetoothDevice device)633 public int getPriority(BluetoothDevice device) { 634 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 635 return mAdapterService.getDatabase() 636 .getProfilePriority(device, BluetoothProfile.A2DP); 637 } 638 isAvrcpAbsoluteVolumeSupported()639 public boolean isAvrcpAbsoluteVolumeSupported() { 640 // TODO (apanicke): Add a hook here for the AvrcpTargetService. 641 return false; 642 } 643 644 setAvrcpAbsoluteVolume(int volume)645 public void setAvrcpAbsoluteVolume(int volume) { 646 // TODO (apanicke): Instead of using A2DP as a middleman for volume changes, add a binder 647 // service to the new AVRCP Profile and have the audio manager use that instead. 648 if (mFactory.getAvrcpTargetService() != null) { 649 mFactory.getAvrcpTargetService().sendVolumeChanged(volume); 650 return; 651 } 652 } 653 isA2dpPlaying(BluetoothDevice device)654 boolean isA2dpPlaying(BluetoothDevice device) { 655 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 656 if (DBG) { 657 Log.d(TAG, "isA2dpPlaying(" + device + ")"); 658 } 659 synchronized (mStateMachines) { 660 A2dpStateMachine sm = mStateMachines.get(device); 661 if (sm == null) { 662 return false; 663 } 664 return sm.isPlaying(); 665 } 666 } 667 668 /** 669 * Gets the current codec status (configuration and capability). 670 * 671 * @param device the remote Bluetooth device. If null, use the currect 672 * active A2DP Bluetooth device. 673 * @return the current codec status 674 * @hide 675 */ getCodecStatus(BluetoothDevice device)676 public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) { 677 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 678 if (DBG) { 679 Log.d(TAG, "getCodecStatus(" + device + ")"); 680 } 681 synchronized (mStateMachines) { 682 if (device == null) { 683 device = mActiveDevice; 684 } 685 if (device == null) { 686 return null; 687 } 688 A2dpStateMachine sm = mStateMachines.get(device); 689 if (sm != null) { 690 return sm.getCodecStatus(); 691 } 692 return null; 693 } 694 } 695 696 /** 697 * Sets the codec configuration preference. 698 * 699 * @param device the remote Bluetooth device. If null, use the currect 700 * active A2DP Bluetooth device. 701 * @param codecConfig the codec configuration preference 702 * @hide 703 */ setCodecConfigPreference(BluetoothDevice device, BluetoothCodecConfig codecConfig)704 public void setCodecConfigPreference(BluetoothDevice device, 705 BluetoothCodecConfig codecConfig) { 706 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 707 if (DBG) { 708 Log.d(TAG, "setCodecConfigPreference(" + device + "): " 709 + Objects.toString(codecConfig)); 710 } 711 if (device == null) { 712 device = mActiveDevice; 713 } 714 if (device == null) { 715 Log.e(TAG, "Cannot set codec config preference: no active A2DP device"); 716 return; 717 } 718 if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) { 719 Log.e(TAG, "Cannot set codec config preference: not supported"); 720 return; 721 } 722 723 BluetoothCodecStatus codecStatus = getCodecStatus(device); 724 if (codecStatus == null) { 725 Log.e(TAG, "Codec status is null on " + device); 726 return; 727 } 728 mA2dpCodecConfig.setCodecConfigPreference(device, codecStatus, codecConfig); 729 } 730 731 /** 732 * Enables the optional codecs. 733 * 734 * @param device the remote Bluetooth device. If null, use the currect 735 * active A2DP Bluetooth device. 736 * @hide 737 */ enableOptionalCodecs(BluetoothDevice device)738 public void enableOptionalCodecs(BluetoothDevice device) { 739 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 740 if (DBG) { 741 Log.d(TAG, "enableOptionalCodecs(" + device + ")"); 742 } 743 if (device == null) { 744 device = mActiveDevice; 745 } 746 if (device == null) { 747 Log.e(TAG, "Cannot enable optional codecs: no active A2DP device"); 748 return; 749 } 750 if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) { 751 Log.e(TAG, "Cannot enable optional codecs: not supported"); 752 return; 753 } 754 BluetoothCodecStatus codecStatus = getCodecStatus(device); 755 if (codecStatus == null) { 756 Log.e(TAG, "Cannot enable optional codecs: codec status is null"); 757 return; 758 } 759 mA2dpCodecConfig.enableOptionalCodecs(device, codecStatus.getCodecConfig()); 760 } 761 762 /** 763 * Disables the optional codecs. 764 * 765 * @param device the remote Bluetooth device. If null, use the currect 766 * active A2DP Bluetooth device. 767 * @hide 768 */ disableOptionalCodecs(BluetoothDevice device)769 public void disableOptionalCodecs(BluetoothDevice device) { 770 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 771 if (DBG) { 772 Log.d(TAG, "disableOptionalCodecs(" + device + ")"); 773 } 774 if (device == null) { 775 device = mActiveDevice; 776 } 777 if (device == null) { 778 Log.e(TAG, "Cannot disable optional codecs: no active A2DP device"); 779 return; 780 } 781 if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) { 782 Log.e(TAG, "Cannot disable optional codecs: not supported"); 783 return; 784 } 785 BluetoothCodecStatus codecStatus = getCodecStatus(device); 786 if (codecStatus == null) { 787 Log.e(TAG, "Cannot disable optional codecs: codec status is null"); 788 return; 789 } 790 mA2dpCodecConfig.disableOptionalCodecs(device, codecStatus.getCodecConfig()); 791 } 792 getSupportsOptionalCodecs(BluetoothDevice device)793 public int getSupportsOptionalCodecs(BluetoothDevice device) { 794 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 795 return mAdapterService.getDatabase().getA2dpSupportsOptionalCodecs(device); 796 } 797 setSupportsOptionalCodecs(BluetoothDevice device, boolean doesSupport)798 public void setSupportsOptionalCodecs(BluetoothDevice device, boolean doesSupport) { 799 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 800 int value = doesSupport ? BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED 801 : BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED; 802 mAdapterService.getDatabase().setA2dpSupportsOptionalCodecs(device, value); 803 } 804 getOptionalCodecsEnabled(BluetoothDevice device)805 public int getOptionalCodecsEnabled(BluetoothDevice device) { 806 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 807 return mAdapterService.getDatabase().getA2dpOptionalCodecsEnabled(device); 808 } 809 setOptionalCodecsEnabled(BluetoothDevice device, int value)810 public void setOptionalCodecsEnabled(BluetoothDevice device, int value) { 811 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 812 if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN 813 && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED 814 && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) { 815 Log.w(TAG, "Unexpected value passed to setOptionalCodecsEnabled:" + value); 816 return; 817 } 818 mAdapterService.getDatabase().setA2dpOptionalCodecsEnabled(device, value); 819 } 820 821 // Handle messages from native (JNI) to Java messageFromNative(A2dpStackEvent stackEvent)822 void messageFromNative(A2dpStackEvent stackEvent) { 823 Objects.requireNonNull(stackEvent.device, 824 "Device should never be null, event: " + stackEvent); 825 synchronized (mStateMachines) { 826 BluetoothDevice device = stackEvent.device; 827 A2dpStateMachine sm = mStateMachines.get(device); 828 if (sm == null) { 829 if (stackEvent.type == A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { 830 switch (stackEvent.valueInt) { 831 case A2dpStackEvent.CONNECTION_STATE_CONNECTED: 832 case A2dpStackEvent.CONNECTION_STATE_CONNECTING: 833 // Create a new state machine only when connecting to a device 834 if (!connectionAllowedCheckMaxDevices(device)) { 835 Log.e(TAG, "Cannot connect to " + device 836 + " : too many connected devices"); 837 return; 838 } 839 sm = getOrCreateStateMachine(device); 840 break; 841 default: 842 break; 843 } 844 } 845 } 846 if (sm == null) { 847 Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent); 848 return; 849 } 850 sm.sendMessage(A2dpStateMachine.STACK_EVENT, stackEvent); 851 } 852 } 853 854 /** 855 * The codec configuration for a device has been updated. 856 * 857 * @param device the remote device 858 * @param codecStatus the new codec status 859 * @param sameAudioFeedingParameters if true the audio feeding parameters 860 * haven't been changed 861 */ 862 @VisibleForTesting codecConfigUpdated(BluetoothDevice device, BluetoothCodecStatus codecStatus, boolean sameAudioFeedingParameters)863 public void codecConfigUpdated(BluetoothDevice device, BluetoothCodecStatus codecStatus, 864 boolean sameAudioFeedingParameters) { 865 // Log codec config and capability metrics 866 BluetoothCodecConfig codecConfig = codecStatus.getCodecConfig(); 867 StatsLog.write(StatsLog.BLUETOOTH_A2DP_CODEC_CONFIG_CHANGED, 868 mAdapterService.obfuscateAddress(device), codecConfig.getCodecType(), 869 codecConfig.getCodecPriority(), codecConfig.getSampleRate(), 870 codecConfig.getBitsPerSample(), codecConfig.getChannelMode(), 871 codecConfig.getCodecSpecific1(), codecConfig.getCodecSpecific2(), 872 codecConfig.getCodecSpecific3(), codecConfig.getCodecSpecific4()); 873 BluetoothCodecConfig[] codecCapabilities = codecStatus.getCodecsSelectableCapabilities(); 874 for (BluetoothCodecConfig codecCapability : codecCapabilities) { 875 StatsLog.write(StatsLog.BLUETOOTH_A2DP_CODEC_CAPABILITY_CHANGED, 876 mAdapterService.obfuscateAddress(device), codecCapability.getCodecType(), 877 codecCapability.getCodecPriority(), codecCapability.getSampleRate(), 878 codecCapability.getBitsPerSample(), codecCapability.getChannelMode(), 879 codecConfig.getCodecSpecific1(), codecConfig.getCodecSpecific2(), 880 codecConfig.getCodecSpecific3(), codecConfig.getCodecSpecific4()); 881 } 882 883 broadcastCodecConfig(device, codecStatus); 884 885 // Inform the Audio Service about the codec configuration change, 886 // so the Audio Service can reset accordingly the audio feeding 887 // parameters in the Audio HAL to the Bluetooth stack. 888 if (isActiveDevice(device) && !sameAudioFeedingParameters) { 889 mAudioManager.handleBluetoothA2dpDeviceConfigChange(device); 890 } 891 } 892 getOrCreateStateMachine(BluetoothDevice device)893 private A2dpStateMachine getOrCreateStateMachine(BluetoothDevice device) { 894 if (device == null) { 895 Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null"); 896 return null; 897 } 898 synchronized (mStateMachines) { 899 A2dpStateMachine sm = mStateMachines.get(device); 900 if (sm != null) { 901 return sm; 902 } 903 // Limit the maximum number of state machines to avoid DoS attack 904 if (mStateMachines.size() >= MAX_A2DP_STATE_MACHINES) { 905 Log.e(TAG, "Maximum number of A2DP state machines reached: " 906 + MAX_A2DP_STATE_MACHINES); 907 return null; 908 } 909 if (DBG) { 910 Log.d(TAG, "Creating a new state machine for " + device); 911 } 912 sm = A2dpStateMachine.make(device, this, mA2dpNativeInterface, 913 mStateMachinesThread.getLooper()); 914 mStateMachines.put(device, sm); 915 return sm; 916 } 917 } 918 919 // This needs to run before any of the Audio Manager connection functions since 920 // AVRCP needs to be aware that the audio device is changed before the Audio Manager 921 // changes the volume of the output devices. updateAndBroadcastActiveDevice(BluetoothDevice device)922 private void updateAndBroadcastActiveDevice(BluetoothDevice device) { 923 if (DBG) { 924 Log.d(TAG, "updateAndBroadcastActiveDevice(" + device + ")"); 925 } 926 927 synchronized (mStateMachines) { 928 if (mFactory.getAvrcpTargetService() != null) { 929 mFactory.getAvrcpTargetService().volumeDeviceSwitched(device); 930 } 931 932 mActiveDevice = device; 933 } 934 935 StatsLog.write(StatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED, BluetoothProfile.A2DP, 936 mAdapterService.obfuscateAddress(device)); 937 Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED); 938 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 939 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT 940 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 941 sendBroadcast(intent, ProfileService.BLUETOOTH_PERM); 942 } 943 broadcastCodecConfig(BluetoothDevice device, BluetoothCodecStatus codecStatus)944 private void broadcastCodecConfig(BluetoothDevice device, BluetoothCodecStatus codecStatus) { 945 if (DBG) { 946 Log.d(TAG, "broadcastCodecConfig(" + device + "): " + codecStatus); 947 } 948 Intent intent = new Intent(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED); 949 intent.putExtra(BluetoothCodecStatus.EXTRA_CODEC_STATUS, codecStatus); 950 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 951 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT 952 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 953 sendBroadcast(intent, A2dpService.BLUETOOTH_PERM); 954 } 955 956 private class BondStateChangedReceiver extends BroadcastReceiver { 957 @Override onReceive(Context context, Intent intent)958 public void onReceive(Context context, Intent intent) { 959 if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { 960 return; 961 } 962 int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 963 BluetoothDevice.ERROR); 964 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 965 Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); 966 bondStateChanged(device, state); 967 } 968 } 969 970 /** 971 * Process a change in the bonding state for a device. 972 * 973 * @param device the device whose bonding state has changed 974 * @param bondState the new bond state for the device. Possible values are: 975 * {@link BluetoothDevice#BOND_NONE}, 976 * {@link BluetoothDevice#BOND_BONDING}, 977 * {@link BluetoothDevice#BOND_BONDED}. 978 */ 979 @VisibleForTesting bondStateChanged(BluetoothDevice device, int bondState)980 void bondStateChanged(BluetoothDevice device, int bondState) { 981 if (DBG) { 982 Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState); 983 } 984 // Remove state machine if the bonding for a device is removed 985 if (bondState != BluetoothDevice.BOND_NONE) { 986 return; 987 } 988 synchronized (mStateMachines) { 989 A2dpStateMachine sm = mStateMachines.get(device); 990 if (sm == null) { 991 return; 992 } 993 if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) { 994 return; 995 } 996 if (mFactory.getAvrcpTargetService() != null) { 997 mFactory.getAvrcpTargetService().removeStoredVolumeForDevice(device); 998 } 999 1000 removeStateMachine(device); 1001 } 1002 } 1003 removeStateMachine(BluetoothDevice device)1004 private void removeStateMachine(BluetoothDevice device) { 1005 synchronized (mStateMachines) { 1006 A2dpStateMachine sm = mStateMachines.get(device); 1007 if (sm == null) { 1008 Log.w(TAG, "removeStateMachine: device " + device 1009 + " does not have a state machine"); 1010 return; 1011 } 1012 Log.i(TAG, "removeStateMachine: removing state machine for device: " + device); 1013 sm.doQuit(); 1014 sm.cleanup(); 1015 mStateMachines.remove(device); 1016 } 1017 } 1018 1019 1020 /** 1021 * Update and initiate optional codec status change to native. 1022 * 1023 * @param device the device to change optional codec status 1024 */ 1025 @VisibleForTesting updateOptionalCodecsSupport(BluetoothDevice device)1026 public void updateOptionalCodecsSupport(BluetoothDevice device) { 1027 int previousSupport = getSupportsOptionalCodecs(device); 1028 boolean supportsOptional = false; 1029 boolean hasMandatoryCodec = false; 1030 1031 synchronized (mStateMachines) { 1032 A2dpStateMachine sm = mStateMachines.get(device); 1033 if (sm == null) { 1034 return; 1035 } 1036 BluetoothCodecStatus codecStatus = sm.getCodecStatus(); 1037 if (codecStatus != null) { 1038 for (BluetoothCodecConfig config : codecStatus.getCodecsSelectableCapabilities()) { 1039 if (config.isMandatoryCodec()) { 1040 hasMandatoryCodec = true; 1041 } else { 1042 supportsOptional = true; 1043 } 1044 } 1045 } 1046 } 1047 if (!hasMandatoryCodec) { 1048 // Mandatory codec(SBC) is not selectable. It could be caused by the remote device 1049 // select codec before native finish get codec capabilities. Stop use this codec 1050 // status as the reference to support/enable optional codecs. 1051 Log.i(TAG, "updateOptionalCodecsSupport: Mandatory codec is not selectable."); 1052 return; 1053 } 1054 1055 if (previousSupport == BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN 1056 || supportsOptional != (previousSupport 1057 == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED)) { 1058 setSupportsOptionalCodecs(device, supportsOptional); 1059 } 1060 if (supportsOptional) { 1061 int enabled = getOptionalCodecsEnabled(device); 1062 switch (enabled) { 1063 case BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN: 1064 // Enable optional codec by default. 1065 setOptionalCodecsEnabled(device, BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED); 1066 // Fall through intended 1067 case BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED: 1068 enableOptionalCodecs(device); 1069 break; 1070 case BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED: 1071 disableOptionalCodecs(device); 1072 break; 1073 } 1074 } 1075 } 1076 connectionStateChanged(BluetoothDevice device, int fromState, int toState)1077 private void connectionStateChanged(BluetoothDevice device, int fromState, int toState) { 1078 if ((device == null) || (fromState == toState)) { 1079 return; 1080 } 1081 synchronized (mStateMachines) { 1082 if (toState == BluetoothProfile.STATE_CONNECTED) { 1083 MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.A2DP); 1084 } 1085 // Set the active device if only one connected device is supported and it was connected 1086 if (toState == BluetoothProfile.STATE_CONNECTED && (mMaxConnectedAudioDevices == 1)) { 1087 setActiveDevice(device); 1088 } 1089 // Check if the active device is not connected anymore 1090 if (isActiveDevice(device) && (fromState == BluetoothProfile.STATE_CONNECTED)) { 1091 setActiveDevice(null); 1092 } 1093 // Check if the device is disconnected - if unbond, remove the state machine 1094 if (toState == BluetoothProfile.STATE_DISCONNECTED) { 1095 int bondState = mAdapterService.getBondState(device); 1096 if (bondState == BluetoothDevice.BOND_NONE) { 1097 if (mFactory.getAvrcpTargetService() != null) { 1098 mFactory.getAvrcpTargetService().removeStoredVolumeForDevice(device); 1099 } 1100 1101 removeStateMachine(device); 1102 } 1103 } 1104 } 1105 } 1106 1107 /** 1108 * Receiver for processing device connection state changes. 1109 * 1110 * <ul> 1111 * <li> Update codec support per device when device is (re)connected 1112 * <li> Delete the state machine instance if the device is disconnected and unbond 1113 * </ul> 1114 */ 1115 private class ConnectionStateChangedReceiver extends BroadcastReceiver { 1116 @Override onReceive(Context context, Intent intent)1117 public void onReceive(Context context, Intent intent) { 1118 if (!BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { 1119 return; 1120 } 1121 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 1122 int toState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 1123 int fromState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); 1124 connectionStateChanged(device, fromState, toState); 1125 } 1126 } 1127 1128 /** 1129 * Binder object: must be a static class or memory leak may occur. 1130 */ 1131 @VisibleForTesting 1132 static class BluetoothA2dpBinder extends IBluetoothA2dp.Stub 1133 implements IProfileServiceBinder { 1134 private A2dpService mService; 1135 getService()1136 private A2dpService getService() { 1137 if (!Utils.checkCaller()) { 1138 Log.w(TAG, "A2DP call not allowed for non-active user"); 1139 return null; 1140 } 1141 1142 if (mService != null && mService.isAvailable()) { 1143 return mService; 1144 } 1145 return null; 1146 } 1147 BluetoothA2dpBinder(A2dpService svc)1148 BluetoothA2dpBinder(A2dpService svc) { 1149 mService = svc; 1150 } 1151 1152 @Override cleanup()1153 public void cleanup() { 1154 mService = null; 1155 } 1156 1157 @Override connect(BluetoothDevice device)1158 public boolean connect(BluetoothDevice device) { 1159 A2dpService service = getService(); 1160 if (service == null) { 1161 return false; 1162 } 1163 return service.connect(device); 1164 } 1165 1166 @Override disconnect(BluetoothDevice device)1167 public boolean disconnect(BluetoothDevice device) { 1168 A2dpService service = getService(); 1169 if (service == null) { 1170 return false; 1171 } 1172 return service.disconnect(device); 1173 } 1174 1175 @Override getConnectedDevices()1176 public List<BluetoothDevice> getConnectedDevices() { 1177 A2dpService service = getService(); 1178 if (service == null) { 1179 return new ArrayList<>(0); 1180 } 1181 return service.getConnectedDevices(); 1182 } 1183 1184 @Override getDevicesMatchingConnectionStates(int[] states)1185 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 1186 A2dpService service = getService(); 1187 if (service == null) { 1188 return new ArrayList<>(0); 1189 } 1190 return service.getDevicesMatchingConnectionStates(states); 1191 } 1192 1193 @Override getConnectionState(BluetoothDevice device)1194 public int getConnectionState(BluetoothDevice device) { 1195 A2dpService service = getService(); 1196 if (service == null) { 1197 return BluetoothProfile.STATE_DISCONNECTED; 1198 } 1199 return service.getConnectionState(device); 1200 } 1201 1202 @Override setActiveDevice(BluetoothDevice device)1203 public boolean setActiveDevice(BluetoothDevice device) { 1204 A2dpService service = getService(); 1205 if (service == null) { 1206 return false; 1207 } 1208 return service.setActiveDevice(device); 1209 } 1210 1211 @Override getActiveDevice()1212 public BluetoothDevice getActiveDevice() { 1213 A2dpService service = getService(); 1214 if (service == null) { 1215 return null; 1216 } 1217 return service.getActiveDevice(); 1218 } 1219 1220 @Override setPriority(BluetoothDevice device, int priority)1221 public boolean setPriority(BluetoothDevice device, int priority) { 1222 A2dpService service = getService(); 1223 if (service == null) { 1224 return false; 1225 } 1226 return service.setPriority(device, priority); 1227 } 1228 1229 @Override getPriority(BluetoothDevice device)1230 public int getPriority(BluetoothDevice device) { 1231 A2dpService service = getService(); 1232 if (service == null) { 1233 return BluetoothProfile.PRIORITY_UNDEFINED; 1234 } 1235 return service.getPriority(device); 1236 } 1237 1238 @Override isAvrcpAbsoluteVolumeSupported()1239 public boolean isAvrcpAbsoluteVolumeSupported() { 1240 A2dpService service = getService(); 1241 if (service == null) { 1242 return false; 1243 } 1244 return service.isAvrcpAbsoluteVolumeSupported(); 1245 } 1246 1247 @Override setAvrcpAbsoluteVolume(int volume)1248 public void setAvrcpAbsoluteVolume(int volume) { 1249 A2dpService service = getService(); 1250 if (service == null) { 1251 return; 1252 } 1253 service.setAvrcpAbsoluteVolume(volume); 1254 } 1255 1256 @Override isA2dpPlaying(BluetoothDevice device)1257 public boolean isA2dpPlaying(BluetoothDevice device) { 1258 A2dpService service = getService(); 1259 if (service == null) { 1260 return false; 1261 } 1262 return service.isA2dpPlaying(device); 1263 } 1264 1265 @Override getCodecStatus(BluetoothDevice device)1266 public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) { 1267 A2dpService service = getService(); 1268 if (service == null) { 1269 return null; 1270 } 1271 return service.getCodecStatus(device); 1272 } 1273 1274 @Override setCodecConfigPreference(BluetoothDevice device, BluetoothCodecConfig codecConfig)1275 public void setCodecConfigPreference(BluetoothDevice device, 1276 BluetoothCodecConfig codecConfig) { 1277 A2dpService service = getService(); 1278 if (service == null) { 1279 return; 1280 } 1281 service.setCodecConfigPreference(device, codecConfig); 1282 } 1283 1284 @Override enableOptionalCodecs(BluetoothDevice device)1285 public void enableOptionalCodecs(BluetoothDevice device) { 1286 A2dpService service = getService(); 1287 if (service == null) { 1288 return; 1289 } 1290 service.enableOptionalCodecs(device); 1291 } 1292 1293 @Override disableOptionalCodecs(BluetoothDevice device)1294 public void disableOptionalCodecs(BluetoothDevice device) { 1295 A2dpService service = getService(); 1296 if (service == null) { 1297 return; 1298 } 1299 service.disableOptionalCodecs(device); 1300 } 1301 supportsOptionalCodecs(BluetoothDevice device)1302 public int supportsOptionalCodecs(BluetoothDevice device) { 1303 A2dpService service = getService(); 1304 if (service == null) { 1305 return BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN; 1306 } 1307 return service.getSupportsOptionalCodecs(device); 1308 } 1309 getOptionalCodecsEnabled(BluetoothDevice device)1310 public int getOptionalCodecsEnabled(BluetoothDevice device) { 1311 A2dpService service = getService(); 1312 if (service == null) { 1313 return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN; 1314 } 1315 return service.getOptionalCodecsEnabled(device); 1316 } 1317 setOptionalCodecsEnabled(BluetoothDevice device, int value)1318 public void setOptionalCodecsEnabled(BluetoothDevice device, int value) { 1319 A2dpService service = getService(); 1320 if (service == null) { 1321 return; 1322 } 1323 service.setOptionalCodecsEnabled(device, value); 1324 } 1325 } 1326 1327 @Override dump(StringBuilder sb)1328 public void dump(StringBuilder sb) { 1329 super.dump(sb); 1330 ProfileService.println(sb, "mActiveDevice: " + mActiveDevice); 1331 for (A2dpStateMachine sm : mStateMachines.values()) { 1332 sm.dump(sb); 1333 } 1334 } 1335 } 1336