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