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