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