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