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.os.ParcelUuid; 31 import android.provider.Settings; 32 import android.util.Log; 33 import com.android.bluetooth.avrcp.Avrcp; 34 import com.android.bluetooth.btservice.ProfileService; 35 import com.android.bluetooth.Utils; 36 import java.util.ArrayList; 37 import java.util.List; 38 import java.util.Objects; 39 40 /** 41 * Provides Bluetooth A2DP profile, as a service in the Bluetooth application. 42 * @hide 43 */ 44 public class A2dpService extends ProfileService { 45 private static final boolean DBG = false; 46 private static final String TAG="A2dpService"; 47 48 private A2dpStateMachine mStateMachine; 49 private Avrcp mAvrcp; 50 51 private BroadcastReceiver mConnectionStateChangedReceiver = new BroadcastReceiver() { 52 @Override 53 public void onReceive(Context context, Intent intent) { 54 if (!BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { 55 return; 56 } 57 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 58 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 59 if (state != BluetoothProfile.STATE_CONNECTED || device == null) { 60 return; 61 } 62 // Each time a device connects, we want to re-check if it supports optional 63 // codecs (perhaps it's had a firmware update, etc.) and save that state if 64 // it differs from what we had saved before. 65 int previousSupport = getSupportsOptionalCodecs(device); 66 boolean supportsOptional = false; 67 for (BluetoothCodecConfig config : 68 mStateMachine.getCodecStatus().getCodecsSelectableCapabilities()) { 69 if (!config.isMandatoryCodec()) { 70 supportsOptional = true; 71 break; 72 } 73 } 74 if (previousSupport == BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN 75 || supportsOptional 76 != (previousSupport == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED)) { 77 setSupportsOptionalCodecs(device, supportsOptional); 78 } 79 if (supportsOptional) { 80 int enabled = getOptionalCodecsEnabled(device); 81 if (enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) { 82 enableOptionalCodecs(); 83 } else if (enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED) { 84 disableOptionalCodecs(); 85 } 86 } 87 } 88 }; 89 90 private static A2dpService sAd2dpService; 91 static final ParcelUuid[] A2DP_SOURCE_UUID = { 92 BluetoothUuid.AudioSource 93 }; 94 static final ParcelUuid[] A2DP_SOURCE_SINK_UUIDS = { 95 BluetoothUuid.AudioSource, 96 BluetoothUuid.AudioSink 97 }; 98 getName()99 protected String getName() { 100 return TAG; 101 } 102 initBinder()103 protected IProfileServiceBinder initBinder() { 104 return new BluetoothA2dpBinder(this); 105 } 106 start()107 protected boolean start() { 108 mAvrcp = Avrcp.make(this); 109 mStateMachine = A2dpStateMachine.make(this, this); 110 setA2dpService(this); 111 IntentFilter filter = new IntentFilter(); 112 filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 113 registerReceiver(mConnectionStateChangedReceiver, filter); 114 return true; 115 } 116 stop()117 protected boolean stop() { 118 if (mStateMachine != null) { 119 mStateMachine.doQuit(); 120 } 121 if (mAvrcp != null) { 122 mAvrcp.doQuit(); 123 } 124 return true; 125 } 126 cleanup()127 protected boolean cleanup() { 128 unregisterReceiver(mConnectionStateChangedReceiver); 129 if (mStateMachine!= null) { 130 mStateMachine.cleanup(); 131 } 132 if (mAvrcp != null) { 133 mAvrcp.cleanup(); 134 mAvrcp = null; 135 } 136 clearA2dpService(); 137 return true; 138 } 139 140 //API Methods 141 getA2dpService()142 public static synchronized A2dpService getA2dpService(){ 143 if (sAd2dpService != null && sAd2dpService.isAvailable()) { 144 if (DBG) Log.d(TAG, "getA2DPService(): returning " + sAd2dpService); 145 return sAd2dpService; 146 } 147 if (DBG) { 148 if (sAd2dpService == null) { 149 Log.d(TAG, "getA2dpService(): service is NULL"); 150 } else if (!(sAd2dpService.isAvailable())) { 151 Log.d(TAG,"getA2dpService(): service is not available"); 152 } 153 } 154 return null; 155 } 156 setA2dpService(A2dpService instance)157 private static synchronized void setA2dpService(A2dpService instance) { 158 if (instance != null && instance.isAvailable()) { 159 if (DBG) Log.d(TAG, "setA2dpService(): set to: " + sAd2dpService); 160 sAd2dpService = instance; 161 } else { 162 if (DBG) { 163 if (sAd2dpService == null) { 164 Log.d(TAG, "setA2dpService(): service not available"); 165 } else if (!sAd2dpService.isAvailable()) { 166 Log.d(TAG,"setA2dpService(): service is cleaning up"); 167 } 168 } 169 } 170 } 171 clearA2dpService()172 private static synchronized void clearA2dpService() { 173 sAd2dpService = null; 174 } 175 connect(BluetoothDevice device)176 public boolean connect(BluetoothDevice device) { 177 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 178 "Need BLUETOOTH ADMIN permission"); 179 180 if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) { 181 return false; 182 } 183 ParcelUuid[] featureUuids = device.getUuids(); 184 if ((BluetoothUuid.containsAnyUuid(featureUuids, A2DP_SOURCE_UUID)) && 185 !(BluetoothUuid.containsAllUuids(featureUuids ,A2DP_SOURCE_SINK_UUIDS))) { 186 Log.e(TAG,"Remote does not have A2dp Sink UUID"); 187 return false; 188 } 189 190 int connectionState = mStateMachine.getConnectionState(device); 191 if (connectionState == BluetoothProfile.STATE_CONNECTED || 192 connectionState == BluetoothProfile.STATE_CONNECTING) { 193 return false; 194 } 195 196 mStateMachine.sendMessage(A2dpStateMachine.CONNECT, device); 197 return true; 198 } 199 disconnect(BluetoothDevice device)200 boolean disconnect(BluetoothDevice device) { 201 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 202 "Need BLUETOOTH ADMIN permission"); 203 int connectionState = mStateMachine.getConnectionState(device); 204 if (connectionState != BluetoothProfile.STATE_CONNECTED && 205 connectionState != BluetoothProfile.STATE_CONNECTING) { 206 return false; 207 } 208 209 mStateMachine.sendMessage(A2dpStateMachine.DISCONNECT, device); 210 return true; 211 } 212 getConnectedDevices()213 public List<BluetoothDevice> getConnectedDevices() { 214 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 215 return mStateMachine.getConnectedDevices(); 216 } 217 getDevicesMatchingConnectionStates(int[] states)218 List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 219 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 220 return mStateMachine.getDevicesMatchingConnectionStates(states); 221 } 222 getConnectionState(BluetoothDevice device)223 public int getConnectionState(BluetoothDevice device) { 224 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 225 return mStateMachine.getConnectionState(device); 226 } 227 setPriority(BluetoothDevice device, int priority)228 public boolean setPriority(BluetoothDevice device, int priority) { 229 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 230 "Need BLUETOOTH_ADMIN permission"); 231 Settings.Global.putInt(getContentResolver(), 232 Settings.Global.getBluetoothA2dpSinkPriorityKey(device.getAddress()), 233 priority); 234 if (DBG) Log.d(TAG,"Saved priority " + device + " = " + priority); 235 return true; 236 } 237 getPriority(BluetoothDevice device)238 public int getPriority(BluetoothDevice device) { 239 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 240 "Need BLUETOOTH_ADMIN permission"); 241 int priority = Settings.Global.getInt(getContentResolver(), 242 Settings.Global.getBluetoothA2dpSinkPriorityKey(device.getAddress()), 243 BluetoothProfile.PRIORITY_UNDEFINED); 244 return priority; 245 } 246 247 /* Absolute volume implementation */ isAvrcpAbsoluteVolumeSupported()248 public boolean isAvrcpAbsoluteVolumeSupported() { 249 return mAvrcp.isAbsoluteVolumeSupported(); 250 } 251 adjustAvrcpAbsoluteVolume(int direction)252 public void adjustAvrcpAbsoluteVolume(int direction) { 253 mAvrcp.adjustVolume(direction); 254 } 255 setAvrcpAbsoluteVolume(int volume)256 public void setAvrcpAbsoluteVolume(int volume) { 257 mAvrcp.setAbsoluteVolume(volume); 258 } 259 setAvrcpAudioState(int state)260 public void setAvrcpAudioState(int state) { 261 mAvrcp.setA2dpAudioState(state); 262 } 263 resetAvrcpBlacklist(BluetoothDevice device)264 public void resetAvrcpBlacklist(BluetoothDevice device) { 265 if (mAvrcp != null) { 266 mAvrcp.resetBlackList(device.getAddress()); 267 } 268 } 269 isA2dpPlaying(BluetoothDevice device)270 synchronized boolean isA2dpPlaying(BluetoothDevice device) { 271 enforceCallingOrSelfPermission(BLUETOOTH_PERM, 272 "Need BLUETOOTH permission"); 273 if (DBG) Log.d(TAG, "isA2dpPlaying(" + device + ")"); 274 return mStateMachine.isPlaying(device); 275 } 276 getCodecStatus()277 public BluetoothCodecStatus getCodecStatus() { 278 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 279 if (DBG) Log.d(TAG, "getCodecStatus()"); 280 return mStateMachine.getCodecStatus(); 281 } 282 setCodecConfigPreference(BluetoothCodecConfig codecConfig)283 public void setCodecConfigPreference(BluetoothCodecConfig codecConfig) { 284 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 285 if (DBG) Log.d(TAG, "setCodecConfigPreference(): " + Objects.toString(codecConfig)); 286 mStateMachine.setCodecConfigPreference(codecConfig); 287 } 288 enableOptionalCodecs()289 public void enableOptionalCodecs() { 290 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 291 if (DBG) Log.d(TAG, "enableOptionalCodecs()"); 292 mStateMachine.enableOptionalCodecs(); 293 } 294 disableOptionalCodecs()295 public void disableOptionalCodecs() { 296 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 297 if (DBG) Log.d(TAG, "disableOptionalCodecs()"); 298 mStateMachine.disableOptionalCodecs(); 299 } 300 getSupportsOptionalCodecs(BluetoothDevice device)301 public int getSupportsOptionalCodecs(BluetoothDevice device) { 302 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 303 int support = Settings.Global.getInt(getContentResolver(), 304 Settings.Global.getBluetoothA2dpSupportsOptionalCodecsKey(device.getAddress()), 305 BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN); 306 return support; 307 } 308 setSupportsOptionalCodecs(BluetoothDevice device, boolean doesSupport)309 public void setSupportsOptionalCodecs(BluetoothDevice device, boolean doesSupport) { 310 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 311 int value = doesSupport ? BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED 312 : BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED; 313 Settings.Global.putInt(getContentResolver(), 314 Settings.Global.getBluetoothA2dpSupportsOptionalCodecsKey(device.getAddress()), 315 value); 316 } 317 getOptionalCodecsEnabled(BluetoothDevice device)318 public int getOptionalCodecsEnabled(BluetoothDevice device) { 319 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 320 return Settings.Global.getInt(getContentResolver(), 321 Settings.Global.getBluetoothA2dpOptionalCodecsEnabledKey(device.getAddress()), 322 BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN); 323 } 324 setOptionalCodecsEnabled(BluetoothDevice device, int value)325 public void setOptionalCodecsEnabled(BluetoothDevice device, int value) { 326 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 327 if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN 328 && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED 329 && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) { 330 Log.w(TAG, "Unexpected value passed to setOptionalCodecsEnabled:" + value); 331 return; 332 } 333 Settings.Global.putInt(getContentResolver(), 334 Settings.Global.getBluetoothA2dpOptionalCodecsEnabledKey(device.getAddress()), 335 value); 336 } 337 338 //Binder object: Must be static class or memory leak may occur 339 private static class BluetoothA2dpBinder extends IBluetoothA2dp.Stub 340 implements IProfileServiceBinder { 341 private A2dpService mService; 342 getService()343 private A2dpService getService() { 344 if (!Utils.checkCaller()) { 345 Log.w(TAG,"A2dp call not allowed for non-active user"); 346 return null; 347 } 348 349 if (mService != null && mService.isAvailable()) { 350 return mService; 351 } 352 return null; 353 } 354 BluetoothA2dpBinder(A2dpService svc)355 BluetoothA2dpBinder(A2dpService svc) { 356 mService = svc; 357 } 358 cleanup()359 public boolean cleanup() { 360 mService = null; 361 return true; 362 } 363 connect(BluetoothDevice device)364 public boolean connect(BluetoothDevice device) { 365 A2dpService service = getService(); 366 if (service == null) return false; 367 return service.connect(device); 368 } 369 disconnect(BluetoothDevice device)370 public boolean disconnect(BluetoothDevice device) { 371 A2dpService service = getService(); 372 if (service == null) return false; 373 return service.disconnect(device); 374 } 375 getConnectedDevices()376 public List<BluetoothDevice> getConnectedDevices() { 377 A2dpService service = getService(); 378 if (service == null) return new ArrayList<BluetoothDevice>(0); 379 return service.getConnectedDevices(); 380 } 381 getDevicesMatchingConnectionStates(int[] states)382 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 383 A2dpService service = getService(); 384 if (service == null) return new ArrayList<BluetoothDevice>(0); 385 return service.getDevicesMatchingConnectionStates(states); 386 } 387 getConnectionState(BluetoothDevice device)388 public int getConnectionState(BluetoothDevice device) { 389 A2dpService service = getService(); 390 if (service == null) return BluetoothProfile.STATE_DISCONNECTED; 391 return service.getConnectionState(device); 392 } 393 setPriority(BluetoothDevice device, int priority)394 public boolean setPriority(BluetoothDevice device, int priority) { 395 A2dpService service = getService(); 396 if (service == null) return false; 397 return service.setPriority(device, priority); 398 } 399 getPriority(BluetoothDevice device)400 public int getPriority(BluetoothDevice device) { 401 A2dpService service = getService(); 402 if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED; 403 return service.getPriority(device); 404 } 405 isAvrcpAbsoluteVolumeSupported()406 public boolean isAvrcpAbsoluteVolumeSupported() { 407 A2dpService service = getService(); 408 if (service == null) return false; 409 return service.isAvrcpAbsoluteVolumeSupported(); 410 } 411 adjustAvrcpAbsoluteVolume(int direction)412 public void adjustAvrcpAbsoluteVolume(int direction) { 413 A2dpService service = getService(); 414 if (service == null) return; 415 service.adjustAvrcpAbsoluteVolume(direction); 416 } 417 setAvrcpAbsoluteVolume(int volume)418 public void setAvrcpAbsoluteVolume(int volume) { 419 A2dpService service = getService(); 420 if (service == null) return; 421 service.setAvrcpAbsoluteVolume(volume); 422 } 423 isA2dpPlaying(BluetoothDevice device)424 public boolean isA2dpPlaying(BluetoothDevice device) { 425 A2dpService service = getService(); 426 if (service == null) return false; 427 return service.isA2dpPlaying(device); 428 } 429 getCodecStatus()430 public BluetoothCodecStatus getCodecStatus() { 431 A2dpService service = getService(); 432 if (service == null) return null; 433 return service.getCodecStatus(); 434 } 435 setCodecConfigPreference(BluetoothCodecConfig codecConfig)436 public void setCodecConfigPreference(BluetoothCodecConfig codecConfig) { 437 A2dpService service = getService(); 438 if (service == null) return; 439 service.setCodecConfigPreference(codecConfig); 440 } 441 enableOptionalCodecs()442 public void enableOptionalCodecs() { 443 A2dpService service = getService(); 444 if (service == null) return; 445 service.enableOptionalCodecs(); 446 } 447 disableOptionalCodecs()448 public void disableOptionalCodecs() { 449 A2dpService service = getService(); 450 if (service == null) return; 451 service.disableOptionalCodecs(); 452 } 453 supportsOptionalCodecs(BluetoothDevice device)454 public int supportsOptionalCodecs(BluetoothDevice device) { 455 A2dpService service = getService(); 456 if (service == null) return BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN; 457 return service.getSupportsOptionalCodecs(device); 458 } 459 getOptionalCodecsEnabled(BluetoothDevice device)460 public int getOptionalCodecsEnabled(BluetoothDevice device) { 461 A2dpService service = getService(); 462 if (service == null) return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN; 463 return service.getOptionalCodecsEnabled(device); 464 } 465 setOptionalCodecsEnabled(BluetoothDevice device, int value)466 public void setOptionalCodecsEnabled(BluetoothDevice device, int value) { 467 A2dpService service = getService(); 468 if (service == null) return; 469 service.setOptionalCodecsEnabled(device, value); 470 } 471 }; 472 473 @Override dump(StringBuilder sb)474 public void dump(StringBuilder sb) { 475 super.dump(sb); 476 if (mStateMachine != null) { 477 mStateMachine.dump(sb); 478 } 479 if (mAvrcp != null) { 480 mAvrcp.dump(sb); 481 } 482 } 483 } 484