1 /* 2 * Copyright (C) 2014 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.bluetooth.a2dpsink; 17 18 import android.annotation.RequiresPermission; 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothAudioConfig; 21 import android.bluetooth.BluetoothDevice; 22 import android.bluetooth.BluetoothProfile; 23 import android.bluetooth.IBluetoothA2dpSink; 24 import android.content.Attributable; 25 import android.content.AttributionSource; 26 import android.media.AudioManager; 27 import android.util.Log; 28 29 import com.android.bluetooth.Utils; 30 import com.android.bluetooth.btservice.AdapterService; 31 import com.android.bluetooth.btservice.ProfileService; 32 import com.android.bluetooth.btservice.storage.DatabaseManager; 33 import com.android.internal.annotations.VisibleForTesting; 34 35 import java.util.ArrayList; 36 import java.util.Arrays; 37 import java.util.List; 38 import java.util.Map; 39 import java.util.Objects; 40 import java.util.concurrent.ConcurrentHashMap; 41 42 /** 43 * Provides Bluetooth A2DP Sink profile, as a service in the Bluetooth application. 44 * @hide 45 */ 46 public class A2dpSinkService extends ProfileService { 47 private static final String TAG = "A2dpSinkService"; 48 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 49 private int mMaxConnectedAudioDevices; 50 51 private AdapterService mAdapterService; 52 private DatabaseManager mDatabaseManager; 53 protected Map<BluetoothDevice, A2dpSinkStateMachine> mDeviceStateMap = 54 new ConcurrentHashMap<>(1); 55 56 private final Object mStreamHandlerLock = new Object(); 57 58 private final Object mActiveDeviceLock = new Object(); 59 private BluetoothDevice mActiveDevice = null; 60 61 private A2dpSinkStreamHandler mA2dpSinkStreamHandler; 62 private static A2dpSinkService sService; 63 64 static { classInitNative()65 classInitNative(); 66 } 67 68 @Override start()69 protected boolean start() { 70 mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), 71 "AdapterService cannot be null when A2dpSinkService starts"); 72 mDatabaseManager = Objects.requireNonNull(AdapterService.getAdapterService().getDatabase(), 73 "DatabaseManager cannot be null when A2dpSinkService starts"); 74 75 synchronized (mStreamHandlerLock) { 76 mA2dpSinkStreamHandler = new A2dpSinkStreamHandler(this, this); 77 } 78 mMaxConnectedAudioDevices = mAdapterService.getMaxConnectedAudioDevices(); 79 initNative(mMaxConnectedAudioDevices); 80 setA2dpSinkService(this); 81 return true; 82 } 83 84 @Override stop()85 protected boolean stop() { 86 setA2dpSinkService(null); 87 cleanupNative(); 88 for (A2dpSinkStateMachine stateMachine : mDeviceStateMap.values()) { 89 stateMachine.quitNow(); 90 } 91 mDeviceStateMap.clear(); 92 synchronized (mStreamHandlerLock) { 93 if (mA2dpSinkStreamHandler != null) { 94 mA2dpSinkStreamHandler.cleanup(); 95 mA2dpSinkStreamHandler = null; 96 } 97 } 98 return true; 99 } 100 getA2dpSinkService()101 public static synchronized A2dpSinkService getA2dpSinkService() { 102 return sService; 103 } 104 105 /** 106 * Testing API to inject a mockA2dpSinkService. 107 * @hide 108 */ 109 @VisibleForTesting setA2dpSinkService(A2dpSinkService service)110 public static synchronized void setA2dpSinkService(A2dpSinkService service) { 111 sService = service; 112 } 113 114 A2dpSinkService()115 public A2dpSinkService() {} 116 117 /** 118 * Set the device that should be allowed to actively stream 119 */ setActiveDevice(BluetoothDevice device)120 public boolean setActiveDevice(BluetoothDevice device) { 121 // Translate to byte address for JNI. Use an all 0 MAC for no active device 122 byte[] address = null; 123 if (device != null) { 124 address = Utils.getByteAddress(device); 125 } else { 126 address = Utils.getBytesFromAddress("00:00:00:00:00:00"); 127 } 128 129 synchronized (mActiveDeviceLock) { 130 if (setActiveDeviceNative(address)) { 131 mActiveDevice = device; 132 return true; 133 } 134 return false; 135 } 136 } 137 138 /** 139 * Get the device that is allowed to be actively streaming 140 */ getActiveDevice()141 public BluetoothDevice getActiveDevice() { 142 synchronized (mActiveDeviceLock) { 143 return mActiveDevice; 144 } 145 } 146 147 /** 148 * Request audio focus such that the designated device can stream audio 149 */ requestAudioFocus(BluetoothDevice device, boolean request)150 public void requestAudioFocus(BluetoothDevice device, boolean request) { 151 synchronized (mStreamHandlerLock) { 152 if (mA2dpSinkStreamHandler == null) return; 153 mA2dpSinkStreamHandler.requestAudioFocus(request); 154 } 155 } 156 157 /** 158 * Get the current Bluetooth Audio focus state 159 * 160 * @return AudioManger.AUDIOFOCUS_* states on success, or AudioManager.ERROR on error 161 */ getFocusState()162 public int getFocusState() { 163 synchronized (mStreamHandlerLock) { 164 if (mA2dpSinkStreamHandler == null) return AudioManager.ERROR; 165 return mA2dpSinkStreamHandler.getFocusState(); 166 } 167 } 168 169 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) isA2dpPlaying(BluetoothDevice device)170 boolean isA2dpPlaying(BluetoothDevice device) { 171 enforceCallingOrSelfPermission( 172 BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission"); 173 synchronized (mStreamHandlerLock) { 174 if (mA2dpSinkStreamHandler == null) return false; 175 return mA2dpSinkStreamHandler.isPlaying(); 176 } 177 } 178 179 @Override initBinder()180 protected IProfileServiceBinder initBinder() { 181 return new A2dpSinkServiceBinder(this); 182 } 183 184 //Binder object: Must be static class or memory leak may occur 185 private static class A2dpSinkServiceBinder extends IBluetoothA2dpSink.Stub 186 implements IProfileServiceBinder { 187 private A2dpSinkService mService; 188 189 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getService(AttributionSource source)190 private A2dpSinkService getService(AttributionSource source) { 191 if (!Utils.checkCallerIsSystemOrActiveUser(TAG) 192 || !Utils.checkServiceAvailable(mService, TAG) 193 || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) { 194 return null; 195 } 196 return mService; 197 } 198 A2dpSinkServiceBinder(A2dpSinkService svc)199 A2dpSinkServiceBinder(A2dpSinkService svc) { 200 mService = svc; 201 } 202 203 @Override cleanup()204 public void cleanup() { 205 mService = null; 206 } 207 208 @Override connect(BluetoothDevice device, AttributionSource source)209 public boolean connect(BluetoothDevice device, AttributionSource source) { 210 Attributable.setAttributionSource(device, source); 211 A2dpSinkService service = getService(source); 212 if (service == null) { 213 return false; 214 } 215 return service.connect(device); 216 } 217 218 @Override disconnect(BluetoothDevice device, AttributionSource source)219 public boolean disconnect(BluetoothDevice device, AttributionSource source) { 220 Attributable.setAttributionSource(device, source); 221 A2dpSinkService service = getService(source); 222 if (service == null) { 223 return false; 224 } 225 return service.disconnect(device); 226 } 227 228 @Override getConnectedDevices(AttributionSource source)229 public List<BluetoothDevice> getConnectedDevices(AttributionSource source) { 230 A2dpSinkService service = getService(source); 231 if (service == null) { 232 return new ArrayList<BluetoothDevice>(0); 233 } 234 return service.getConnectedDevices(); 235 } 236 237 @Override getDevicesMatchingConnectionStates(int[] states, AttributionSource source)238 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states, 239 AttributionSource source) { 240 A2dpSinkService service = getService(source); 241 if (service == null) { 242 return new ArrayList<BluetoothDevice>(0); 243 } 244 return service.getDevicesMatchingConnectionStates(states); 245 } 246 247 @Override getConnectionState(BluetoothDevice device, AttributionSource source)248 public int getConnectionState(BluetoothDevice device, AttributionSource source) { 249 Attributable.setAttributionSource(device, source); 250 A2dpSinkService service = getService(source); 251 if (service == null) { 252 return BluetoothProfile.STATE_DISCONNECTED; 253 } 254 return service.getConnectionState(device); 255 } 256 257 @Override setConnectionPolicy(BluetoothDevice device, int connectionPolicy, AttributionSource source)258 public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy, 259 AttributionSource source) { 260 Attributable.setAttributionSource(device, source); 261 A2dpSinkService service = getService(source); 262 if (service == null) { 263 return false; 264 } 265 return service.setConnectionPolicy(device, connectionPolicy); 266 } 267 268 @Override getConnectionPolicy(BluetoothDevice device, AttributionSource source)269 public int getConnectionPolicy(BluetoothDevice device, AttributionSource source) { 270 Attributable.setAttributionSource(device, source); 271 A2dpSinkService service = getService(source); 272 if (service == null) { 273 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; 274 } 275 return service.getConnectionPolicy(device); 276 } 277 278 @Override isA2dpPlaying(BluetoothDevice device, AttributionSource source)279 public boolean isA2dpPlaying(BluetoothDevice device, AttributionSource source) { 280 Attributable.setAttributionSource(device, source); 281 A2dpSinkService service = getService(source); 282 if (service == null) { 283 return false; 284 } 285 return service.isA2dpPlaying(device); 286 } 287 288 @Override getAudioConfig(BluetoothDevice device, AttributionSource source)289 public BluetoothAudioConfig getAudioConfig(BluetoothDevice device, 290 AttributionSource source) { 291 Attributable.setAttributionSource(device, source); 292 A2dpSinkService service = getService(source); 293 if (service == null) { 294 return null; 295 } 296 return service.getAudioConfig(device); 297 } 298 } 299 300 /* Generic Profile Code */ 301 302 /** 303 * Connect the given Bluetooth device. 304 * 305 * @return true if connection is successful, false otherwise. 306 */ 307 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) connect(BluetoothDevice device)308 public boolean connect(BluetoothDevice device) { 309 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 310 "Need BLUETOOTH_PRIVILEGED permission"); 311 if (device == null) { 312 throw new IllegalArgumentException("Null device"); 313 } 314 if (DBG) { 315 StringBuilder sb = new StringBuilder(); 316 dump(sb); 317 Log.d(TAG, " connect device: " + device 318 + ", InstanceMap start state: " + sb.toString()); 319 } 320 if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 321 Log.w(TAG, "Connection not allowed: <" + device.getAddress() 322 + "> is CONNECTION_POLICY_FORBIDDEN"); 323 return false; 324 } 325 326 A2dpSinkStateMachine stateMachine = getOrCreateStateMachine(device); 327 if (stateMachine != null) { 328 stateMachine.connect(); 329 return true; 330 } else { 331 // a state machine instance doesn't exist yet, and the max has been reached. 332 Log.e(TAG, "Maxed out on the number of allowed A2DP Sink connections. " 333 + "Connect request rejected on " + device); 334 return false; 335 } 336 } 337 338 /** 339 * Disconnect the given Bluetooth device. 340 * 341 * @return true if disconnect is successful, false otherwise. 342 */ disconnect(BluetoothDevice device)343 public boolean disconnect(BluetoothDevice device) { 344 if (DBG) { 345 StringBuilder sb = new StringBuilder(); 346 dump(sb); 347 Log.d(TAG, "A2DP disconnect device: " + device 348 + ", InstanceMap start state: " + sb.toString()); 349 } 350 351 A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device); 352 // a state machine instance doesn't exist. maybe it is already gone? 353 if (stateMachine == null) { 354 return false; 355 } 356 int connectionState = stateMachine.getState(); 357 if (connectionState == BluetoothProfile.STATE_DISCONNECTED 358 || connectionState == BluetoothProfile.STATE_DISCONNECTING) { 359 return false; 360 } 361 // upon completion of disconnect, the state machine will remove itself from the available 362 // devices map 363 stateMachine.disconnect(); 364 return true; 365 } 366 removeStateMachine(A2dpSinkStateMachine stateMachine)367 void removeStateMachine(A2dpSinkStateMachine stateMachine) { 368 mDeviceStateMap.remove(stateMachine.getDevice()); 369 } 370 getConnectedDevices()371 public List<BluetoothDevice> getConnectedDevices() { 372 return getDevicesMatchingConnectionStates(new int[]{BluetoothAdapter.STATE_CONNECTED}); 373 } 374 getOrCreateStateMachine(BluetoothDevice device)375 protected A2dpSinkStateMachine getOrCreateStateMachine(BluetoothDevice device) { 376 A2dpSinkStateMachine newStateMachine = new A2dpSinkStateMachine(device, this); 377 A2dpSinkStateMachine existingStateMachine = 378 mDeviceStateMap.putIfAbsent(device, newStateMachine); 379 // Given null is not a valid value in our map, ConcurrentHashMap will return null if the 380 // key was absent and our new value was added. We should then start and return it. 381 if (existingStateMachine == null) { 382 newStateMachine.start(); 383 return newStateMachine; 384 } 385 return existingStateMachine; 386 } 387 getDevicesMatchingConnectionStates(int[] states)388 List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 389 if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states)); 390 List<BluetoothDevice> deviceList = new ArrayList<>(); 391 BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); 392 int connectionState; 393 for (BluetoothDevice device : bondedDevices) { 394 connectionState = getConnectionState(device); 395 if (DBG) Log.d(TAG, "Device: " + device + "State: " + connectionState); 396 for (int i = 0; i < states.length; i++) { 397 if (connectionState == states[i]) { 398 deviceList.add(device); 399 } 400 } 401 } 402 if (DBG) Log.d(TAG, deviceList.toString()); 403 Log.d(TAG, "GetDevicesDone"); 404 return deviceList; 405 } 406 407 /** 408 * Get the current connection state of the profile 409 * 410 * @param device is the remote bluetooth device 411 * @return {@link BluetoothProfile#STATE_DISCONNECTED} if this profile is disconnected, 412 * {@link BluetoothProfile#STATE_CONNECTING} if this profile is being connected, 413 * {@link BluetoothProfile#STATE_CONNECTED} if this profile is connected, or 414 * {@link BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected 415 */ getConnectionState(BluetoothDevice device)416 public int getConnectionState(BluetoothDevice device) { 417 A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device); 418 return (stateMachine == null) ? BluetoothProfile.STATE_DISCONNECTED 419 : stateMachine.getState(); 420 } 421 422 /** 423 * Set connection policy of the profile and connects it if connectionPolicy is 424 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is 425 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN} 426 * 427 * <p> The device should already be paired. 428 * Connection policy can be one of: 429 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 430 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, 431 * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 432 * 433 * @param device Paired bluetooth device 434 * @param connectionPolicy is the connection policy to set to for this profile 435 * @return true if connectionPolicy is set, false on error 436 */ 437 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) setConnectionPolicy(BluetoothDevice device, int connectionPolicy)438 public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { 439 enforceCallingOrSelfPermission( 440 BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission"); 441 if (DBG) { 442 Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy); 443 } 444 445 if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.A2DP_SINK, 446 connectionPolicy)) { 447 return false; 448 } 449 if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 450 connect(device); 451 } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 452 disconnect(device); 453 } 454 return true; 455 } 456 457 /** 458 * Get the connection policy of the profile. 459 * 460 * @param device the remote device 461 * @return connection policy of the specified device 462 */ 463 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) getConnectionPolicy(BluetoothDevice device)464 public int getConnectionPolicy(BluetoothDevice device) { 465 enforceCallingOrSelfPermission( 466 BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission"); 467 return mDatabaseManager 468 .getProfileConnectionPolicy(device, BluetoothProfile.A2DP_SINK); 469 } 470 471 472 @Override dump(StringBuilder sb)473 public void dump(StringBuilder sb) { 474 super.dump(sb); 475 ProfileService.println(sb, "Active Device = " + getActiveDevice()); 476 ProfileService.println(sb, "Max Connected Devices = " + mMaxConnectedAudioDevices); 477 ProfileService.println(sb, "Devices Tracked = " + mDeviceStateMap.size()); 478 for (A2dpSinkStateMachine stateMachine : mDeviceStateMap.values()) { 479 ProfileService.println(sb, 480 "==== StateMachine for " + stateMachine.getDevice() + " ===="); 481 stateMachine.dump(sb); 482 } 483 } 484 getAudioConfig(BluetoothDevice device)485 BluetoothAudioConfig getAudioConfig(BluetoothDevice device) { 486 A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device); 487 // a state machine instance doesn't exist. maybe it is already gone? 488 if (stateMachine == null) { 489 return null; 490 } 491 return stateMachine.getAudioConfig(); 492 } 493 494 /* JNI interfaces*/ 495 classInitNative()496 private static native void classInitNative(); 497 initNative(int maxConnectedAudioDevices)498 private native void initNative(int maxConnectedAudioDevices); 499 cleanupNative()500 private native void cleanupNative(); 501 connectA2dpNative(byte[] address)502 native boolean connectA2dpNative(byte[] address); 503 disconnectA2dpNative(byte[] address)504 native boolean disconnectA2dpNative(byte[] address); 505 506 /** 507 * set A2DP state machine as the active device 508 * the active device is the only one that will receive passthrough commands and the only one 509 * that will have its audio decoded 510 * 511 * @hide 512 * @param address 513 * @return active device request has been scheduled 514 */ setActiveDeviceNative(byte[] address)515 public native boolean setActiveDeviceNative(byte[] address); 516 517 /** 518 * inform A2DP decoder of the current audio focus 519 * 520 * @param focusGranted 521 */ 522 @VisibleForTesting informAudioFocusStateNative(int focusGranted)523 public native void informAudioFocusStateNative(int focusGranted); 524 525 /** 526 * inform A2DP decoder the desired audio gain 527 * 528 * @param gain 529 */ 530 @VisibleForTesting informAudioTrackGainNative(float gain)531 public native void informAudioTrackGainNative(float gain); 532 onConnectionStateChanged(byte[] address, int state)533 private void onConnectionStateChanged(byte[] address, int state) { 534 StackEvent event = StackEvent.connectionStateChanged(getAnonymousDevice(address), state); 535 A2dpSinkStateMachine stateMachine = getOrCreateStateMachine(event.mDevice); 536 stateMachine.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event); 537 } 538 onAudioStateChanged(byte[] address, int state)539 private void onAudioStateChanged(byte[] address, int state) { 540 synchronized (mStreamHandlerLock) { 541 if (mA2dpSinkStreamHandler == null) { 542 Log.e(TAG, "Received audio state change before we've been started"); 543 return; 544 } else if (state == StackEvent.AUDIO_STATE_STARTED) { 545 mA2dpSinkStreamHandler.obtainMessage( 546 A2dpSinkStreamHandler.SRC_STR_START).sendToTarget(); 547 } else if (state == StackEvent.AUDIO_STATE_STOPPED 548 || state == StackEvent.AUDIO_STATE_REMOTE_SUSPEND) { 549 mA2dpSinkStreamHandler.obtainMessage( 550 A2dpSinkStreamHandler.SRC_STR_STOP).sendToTarget(); 551 } 552 } 553 } 554 onAudioConfigChanged(byte[] address, int sampleRate, int channelCount)555 private void onAudioConfigChanged(byte[] address, int sampleRate, int channelCount) { 556 StackEvent event = StackEvent.audioConfigChanged(getAnonymousDevice(address), sampleRate, 557 channelCount); 558 A2dpSinkStateMachine stateMachine = getOrCreateStateMachine(event.mDevice); 559 stateMachine.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event); 560 } 561 } 562