1/* 2 * Copyright (C) 2008 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/** 18 * TODO: Move this to services.jar 19 * and make the constructor package private again. 20 * @hide 21 */ 22 23package android.server; 24 25import android.bluetooth.BluetoothA2dp; 26import android.bluetooth.BluetoothAdapter; 27import android.bluetooth.BluetoothDevice; 28import android.bluetooth.BluetoothProfile; 29import android.bluetooth.BluetoothUuid; 30import android.bluetooth.IBluetoothA2dp; 31import android.content.BroadcastReceiver; 32import android.content.Context; 33import android.content.Intent; 34import android.content.IntentFilter; 35import android.media.AudioManager; 36import android.os.Handler; 37import android.os.Message; 38import android.os.ParcelUuid; 39import android.os.PowerManager; 40import android.os.PowerManager.WakeLock; 41import android.provider.Settings; 42import android.util.Log; 43 44import java.io.FileDescriptor; 45import java.io.PrintWriter; 46import java.util.ArrayList; 47import java.util.HashMap; 48import java.util.List; 49 50 51public class BluetoothA2dpService extends IBluetoothA2dp.Stub { 52 private static final String TAG = "BluetoothA2dpService"; 53 private static final boolean DBG = true; 54 55 public static final String BLUETOOTH_A2DP_SERVICE = "bluetooth_a2dp"; 56 57 private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; 58 private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; 59 60 private static final String BLUETOOTH_ENABLED = "bluetooth_enabled"; 61 62 private static final String PROPERTY_STATE = "State"; 63 64 private final Context mContext; 65 private final IntentFilter mIntentFilter; 66 private HashMap<BluetoothDevice, Integer> mAudioDevices; 67 private final AudioManager mAudioManager; 68 private final BluetoothService mBluetoothService; 69 private final BluetoothAdapter mAdapter; 70 private int mTargetA2dpState; 71 private BluetoothDevice mPlayingA2dpDevice; 72 private IntentBroadcastHandler mIntentBroadcastHandler; 73 private final WakeLock mWakeLock; 74 75 private static final int MSG_CONNECTION_STATE_CHANGED = 0; 76 77 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 78 @Override 79 public void onReceive(Context context, Intent intent) { 80 String action = intent.getAction(); 81 BluetoothDevice device = 82 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 83 if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { 84 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 85 BluetoothAdapter.ERROR); 86 switch (state) { 87 case BluetoothAdapter.STATE_ON: 88 onBluetoothEnable(); 89 break; 90 case BluetoothAdapter.STATE_TURNING_OFF: 91 onBluetoothDisable(); 92 break; 93 } 94 } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { 95 synchronized (this) { 96 if (mAudioDevices.containsKey(device)) { 97 int state = mAudioDevices.get(device); 98 handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED); 99 } 100 } 101 } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) { 102 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 103 if (streamType == AudioManager.STREAM_MUSIC) { 104 List<BluetoothDevice> sinks = getConnectedDevices(); 105 106 if (sinks.size() != 0 && isPhoneDocked(sinks.get(0))) { 107 String address = sinks.get(0).getAddress(); 108 int newVolLevel = 109 intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0); 110 int oldVolLevel = 111 intent.getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 0); 112 String path = mBluetoothService.getObjectPathFromAddress(address); 113 if (newVolLevel > oldVolLevel) { 114 avrcpVolumeUpNative(path); 115 } else if (newVolLevel < oldVolLevel) { 116 avrcpVolumeDownNative(path); 117 } 118 } 119 } 120 } 121 } 122 }; 123 124 private boolean isPhoneDocked(BluetoothDevice device) { 125 // This works only because these broadcast intents are "sticky" 126 Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); 127 if (i != null) { 128 int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED); 129 if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) { 130 BluetoothDevice dockDevice = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 131 if (dockDevice != null && device.equals(dockDevice)) { 132 return true; 133 } 134 } 135 } 136 return false; 137 } 138 139 public BluetoothA2dpService(Context context, BluetoothService bluetoothService) { 140 mContext = context; 141 142 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); 143 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "BluetoothA2dpService"); 144 145 mIntentBroadcastHandler = new IntentBroadcastHandler(); 146 147 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 148 149 mBluetoothService = bluetoothService; 150 if (mBluetoothService == null) { 151 throw new RuntimeException("Platform does not support Bluetooth"); 152 } 153 154 if (!initNative()) { 155 throw new RuntimeException("Could not init BluetoothA2dpService"); 156 } 157 158 mAdapter = BluetoothAdapter.getDefaultAdapter(); 159 160 mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); 161 mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); 162 mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); 163 mIntentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION); 164 mContext.registerReceiver(mReceiver, mIntentFilter); 165 166 mAudioDevices = new HashMap<BluetoothDevice, Integer>(); 167 168 if (mBluetoothService.isEnabled()) 169 onBluetoothEnable(); 170 mTargetA2dpState = -1; 171 mBluetoothService.setA2dpService(this); 172 } 173 174 @Override 175 protected void finalize() throws Throwable { 176 try { 177 cleanupNative(); 178 } finally { 179 super.finalize(); 180 } 181 } 182 183 private int convertBluezSinkStringToState(String value) { 184 if (value.equalsIgnoreCase("disconnected")) 185 return BluetoothA2dp.STATE_DISCONNECTED; 186 if (value.equalsIgnoreCase("connecting")) 187 return BluetoothA2dp.STATE_CONNECTING; 188 if (value.equalsIgnoreCase("connected")) 189 return BluetoothA2dp.STATE_CONNECTED; 190 if (value.equalsIgnoreCase("playing")) 191 return BluetoothA2dp.STATE_PLAYING; 192 return -1; 193 } 194 195 private boolean isSinkDevice(BluetoothDevice device) { 196 ParcelUuid[] uuids = mBluetoothService.getRemoteUuids(device.getAddress()); 197 if (uuids != null && BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSink)) { 198 return true; 199 } 200 return false; 201 } 202 203 private synchronized void addAudioSink(BluetoothDevice device) { 204 if (mAudioDevices.get(device) == null) { 205 mAudioDevices.put(device, BluetoothA2dp.STATE_DISCONNECTED); 206 } 207 } 208 209 private synchronized void onBluetoothEnable() { 210 String devices = mBluetoothService.getProperty("Devices", true); 211 if (devices != null) { 212 String [] paths = devices.split(","); 213 for (String path: paths) { 214 String address = mBluetoothService.getAddressFromObjectPath(path); 215 BluetoothDevice device = mAdapter.getRemoteDevice(address); 216 ParcelUuid[] remoteUuids = mBluetoothService.getRemoteUuids(address); 217 if (remoteUuids != null) 218 if (BluetoothUuid.containsAnyUuid(remoteUuids, 219 new ParcelUuid[] {BluetoothUuid.AudioSink, 220 BluetoothUuid.AdvAudioDist})) { 221 addAudioSink(device); 222 } 223 } 224 } 225 mAudioManager.setParameters(BLUETOOTH_ENABLED+"=true"); 226 mAudioManager.setParameters("A2dpSuspended=false"); 227 } 228 229 private synchronized void onBluetoothDisable() { 230 if (!mAudioDevices.isEmpty()) { 231 BluetoothDevice[] devices = new BluetoothDevice[mAudioDevices.size()]; 232 devices = mAudioDevices.keySet().toArray(devices); 233 for (BluetoothDevice device : devices) { 234 int state = getConnectionState(device); 235 switch (state) { 236 case BluetoothA2dp.STATE_CONNECTING: 237 case BluetoothA2dp.STATE_CONNECTED: 238 case BluetoothA2dp.STATE_PLAYING: 239 disconnectSinkNative(mBluetoothService.getObjectPathFromAddress( 240 device.getAddress())); 241 handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED); 242 break; 243 case BluetoothA2dp.STATE_DISCONNECTING: 244 handleSinkStateChange(device, BluetoothA2dp.STATE_DISCONNECTING, 245 BluetoothA2dp.STATE_DISCONNECTED); 246 break; 247 } 248 } 249 mAudioDevices.clear(); 250 } 251 252 mAudioManager.setParameters(BLUETOOTH_ENABLED + "=false"); 253 } 254 255 private synchronized boolean isConnectSinkFeasible(BluetoothDevice device) { 256 if (!mBluetoothService.isEnabled() || !isSinkDevice(device) || 257 getPriority(device) == BluetoothA2dp.PRIORITY_OFF) { 258 return false; 259 } 260 261 addAudioSink(device); 262 263 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress()); 264 if (path == null) { 265 return false; 266 } 267 return true; 268 } 269 270 public synchronized boolean isA2dpPlaying(BluetoothDevice device) { 271 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 272 "Need BLUETOOTH_ADMIN permission"); 273 if (DBG) log("isA2dpPlaying(" + device + ")"); 274 if (device.equals(mPlayingA2dpDevice)) return true; 275 return false; 276 } 277 278 public synchronized boolean connect(BluetoothDevice device) { 279 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 280 "Need BLUETOOTH_ADMIN permission"); 281 if (DBG) log("connectSink(" + device + ")"); 282 if (!isConnectSinkFeasible(device)) return false; 283 284 for (BluetoothDevice sinkDevice : mAudioDevices.keySet()) { 285 if (getConnectionState(sinkDevice) != BluetoothProfile.STATE_DISCONNECTED) { 286 disconnect(sinkDevice); 287 } 288 } 289 290 return mBluetoothService.connectSink(device.getAddress()); 291 } 292 293 public synchronized boolean connectSinkInternal(BluetoothDevice device) { 294 if (!mBluetoothService.isEnabled()) return false; 295 296 int state = mAudioDevices.get(device); 297 298 // ignore if there are any active sinks 299 if (getDevicesMatchingConnectionStates(new int[] { 300 BluetoothA2dp.STATE_CONNECTING, 301 BluetoothA2dp.STATE_CONNECTED, 302 BluetoothA2dp.STATE_DISCONNECTING}).size() != 0) { 303 return false; 304 } 305 306 switch (state) { 307 case BluetoothA2dp.STATE_CONNECTED: 308 case BluetoothA2dp.STATE_DISCONNECTING: 309 return false; 310 case BluetoothA2dp.STATE_CONNECTING: 311 return true; 312 } 313 314 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress()); 315 316 // State is DISCONNECTED and we are connecting. 317 if (getPriority(device) < BluetoothA2dp.PRIORITY_AUTO_CONNECT) { 318 setPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT); 319 } 320 handleSinkStateChange(device, state, BluetoothA2dp.STATE_CONNECTING); 321 322 if (!connectSinkNative(path)) { 323 // Restore previous state 324 handleSinkStateChange(device, mAudioDevices.get(device), state); 325 return false; 326 } 327 return true; 328 } 329 330 private synchronized boolean isDisconnectSinkFeasible(BluetoothDevice device) { 331 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress()); 332 if (path == null) { 333 return false; 334 } 335 336 int state = getConnectionState(device); 337 switch (state) { 338 case BluetoothA2dp.STATE_DISCONNECTED: 339 case BluetoothA2dp.STATE_DISCONNECTING: 340 return false; 341 } 342 return true; 343 } 344 345 public synchronized boolean disconnect(BluetoothDevice device) { 346 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 347 "Need BLUETOOTH_ADMIN permission"); 348 if (DBG) log("disconnectSink(" + device + ")"); 349 if (!isDisconnectSinkFeasible(device)) return false; 350 return mBluetoothService.disconnectSink(device.getAddress()); 351 } 352 353 public synchronized boolean disconnectSinkInternal(BluetoothDevice device) { 354 int state = getConnectionState(device); 355 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress()); 356 357 switch (state) { 358 case BluetoothA2dp.STATE_DISCONNECTED: 359 case BluetoothA2dp.STATE_DISCONNECTING: 360 return false; 361 } 362 // State is CONNECTING or CONNECTED or PLAYING 363 handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTING); 364 if (!disconnectSinkNative(path)) { 365 // Restore previous state 366 handleSinkStateChange(device, mAudioDevices.get(device), state); 367 return false; 368 } 369 return true; 370 } 371 372 public synchronized boolean suspendSink(BluetoothDevice device) { 373 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 374 "Need BLUETOOTH_ADMIN permission"); 375 if (DBG) log("suspendSink(" + device + "), mTargetA2dpState: "+mTargetA2dpState); 376 if (device == null || mAudioDevices == null) { 377 return false; 378 } 379 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress()); 380 Integer state = mAudioDevices.get(device); 381 if (path == null || state == null) { 382 return false; 383 } 384 385 mTargetA2dpState = BluetoothA2dp.STATE_CONNECTED; 386 return checkSinkSuspendState(state.intValue()); 387 } 388 389 public synchronized boolean resumeSink(BluetoothDevice device) { 390 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 391 "Need BLUETOOTH_ADMIN permission"); 392 if (DBG) log("resumeSink(" + device + "), mTargetA2dpState: "+mTargetA2dpState); 393 if (device == null || mAudioDevices == null) { 394 return false; 395 } 396 String path = mBluetoothService.getObjectPathFromAddress(device.getAddress()); 397 Integer state = mAudioDevices.get(device); 398 if (path == null || state == null) { 399 return false; 400 } 401 mTargetA2dpState = BluetoothA2dp.STATE_PLAYING; 402 return checkSinkSuspendState(state.intValue()); 403 } 404 405 public synchronized int getConnectionState(BluetoothDevice device) { 406 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 407 Integer state = mAudioDevices.get(device); 408 if (state == null) 409 return BluetoothA2dp.STATE_DISCONNECTED; 410 return state; 411 } 412 413 public synchronized List<BluetoothDevice> getConnectedDevices() { 414 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 415 List<BluetoothDevice> sinks = getDevicesMatchingConnectionStates( 416 new int[] {BluetoothA2dp.STATE_CONNECTED}); 417 return sinks; 418 } 419 420 public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 421 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 422 ArrayList<BluetoothDevice> sinks = new ArrayList<BluetoothDevice>(); 423 for (BluetoothDevice device: mAudioDevices.keySet()) { 424 int sinkState = getConnectionState(device); 425 for (int state : states) { 426 if (state == sinkState) { 427 sinks.add(device); 428 break; 429 } 430 } 431 } 432 return sinks; 433 } 434 435 public synchronized int getPriority(BluetoothDevice device) { 436 mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 437 return Settings.Secure.getInt(mContext.getContentResolver(), 438 Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()), 439 BluetoothA2dp.PRIORITY_UNDEFINED); 440 } 441 442 public synchronized boolean setPriority(BluetoothDevice device, int priority) { 443 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 444 "Need BLUETOOTH_ADMIN permission"); 445 return Settings.Secure.putInt(mContext.getContentResolver(), 446 Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()), priority); 447 } 448 449 public synchronized boolean allowIncomingConnect(BluetoothDevice device, boolean value) { 450 mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 451 "Need BLUETOOTH_ADMIN permission"); 452 String address = device.getAddress(); 453 if (!BluetoothAdapter.checkBluetoothAddress(address)) { 454 return false; 455 } 456 Integer data = mBluetoothService.getAuthorizationAgentRequestData(address); 457 if (data == null) { 458 Log.w(TAG, "allowIncomingConnect(" + device + ") called but no native data available"); 459 return false; 460 } 461 log("allowIncomingConnect: A2DP: " + device + ":" + value); 462 return mBluetoothService.setAuthorizationNative(address, value, data.intValue()); 463 } 464 465 /** 466 * Called by native code on a PropertyChanged signal from 467 * org.bluez.AudioSink. 468 * 469 * @param path the object path for the changed device 470 * @param propValues a string array containing the key and one or more 471 * values. 472 */ 473 private synchronized void onSinkPropertyChanged(String path, String[] propValues) { 474 if (!mBluetoothService.isEnabled()) { 475 return; 476 } 477 478 String name = propValues[0]; 479 String address = mBluetoothService.getAddressFromObjectPath(path); 480 if (address == null) { 481 Log.e(TAG, "onSinkPropertyChanged: Address of the remote device in null"); 482 return; 483 } 484 485 BluetoothDevice device = mAdapter.getRemoteDevice(address); 486 487 if (name.equals(PROPERTY_STATE)) { 488 int state = convertBluezSinkStringToState(propValues[1]); 489 log("A2DP: onSinkPropertyChanged newState is: " + state + "mPlayingA2dpDevice: " + mPlayingA2dpDevice); 490 491 if (mAudioDevices.get(device) == null) { 492 // This is for an incoming connection for a device not known to us. 493 // We have authorized it and bluez state has changed. 494 addAudioSink(device); 495 handleSinkStateChange(device, BluetoothA2dp.STATE_DISCONNECTED, state); 496 } else { 497 if (state == BluetoothA2dp.STATE_PLAYING && mPlayingA2dpDevice == null) { 498 mPlayingA2dpDevice = device; 499 handleSinkPlayingStateChange(device, state, BluetoothA2dp.STATE_NOT_PLAYING); 500 } else if (state == BluetoothA2dp.STATE_CONNECTED && mPlayingA2dpDevice != null) { 501 mPlayingA2dpDevice = null; 502 handleSinkPlayingStateChange(device, BluetoothA2dp.STATE_NOT_PLAYING, 503 BluetoothA2dp.STATE_PLAYING); 504 } else { 505 mPlayingA2dpDevice = null; 506 int prevState = mAudioDevices.get(device); 507 handleSinkStateChange(device, prevState, state); 508 } 509 } 510 } 511 } 512 513 private void handleSinkStateChange(BluetoothDevice device, int prevState, int state) { 514 if (state != prevState) { 515 mAudioDevices.put(device, state); 516 517 checkSinkSuspendState(state); 518 mTargetA2dpState = -1; 519 520 if (getPriority(device) > BluetoothA2dp.PRIORITY_OFF && 521 state == BluetoothA2dp.STATE_CONNECTED) { 522 // We have connected or attempting to connect. 523 // Bump priority 524 setPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT); 525 // We will only have 1 device with AUTO_CONNECT priority 526 // To be backward compatible set everyone else to have PRIORITY_ON 527 adjustOtherSinkPriorities(device); 528 } 529 530 int delay = mAudioManager.setBluetoothA2dpDeviceConnectionState(device, state); 531 532 mWakeLock.acquire(); 533 mIntentBroadcastHandler.sendMessageDelayed(mIntentBroadcastHandler.obtainMessage( 534 MSG_CONNECTION_STATE_CHANGED, 535 prevState, 536 state, 537 device), 538 delay); 539 } 540 } 541 542 private void handleSinkPlayingStateChange(BluetoothDevice device, int state, int prevState) { 543 Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED); 544 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 545 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); 546 intent.putExtra(BluetoothProfile.EXTRA_STATE, state); 547 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 548 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 549 550 if (DBG) log("A2DP Playing state : device: " + device + " State:" + prevState + "->" + state); 551 } 552 553 private void adjustOtherSinkPriorities(BluetoothDevice connectedDevice) { 554 for (BluetoothDevice device : mAdapter.getBondedDevices()) { 555 if (getPriority(device) >= BluetoothA2dp.PRIORITY_AUTO_CONNECT && 556 !device.equals(connectedDevice)) { 557 setPriority(device, BluetoothA2dp.PRIORITY_ON); 558 } 559 } 560 } 561 562 private boolean checkSinkSuspendState(int state) { 563 boolean result = true; 564 565 if (state != mTargetA2dpState) { 566 if (state == BluetoothA2dp.STATE_PLAYING && 567 mTargetA2dpState == BluetoothA2dp.STATE_CONNECTED) { 568 mAudioManager.setParameters("A2dpSuspended=true"); 569 } else if (state == BluetoothA2dp.STATE_CONNECTED && 570 mTargetA2dpState == BluetoothA2dp.STATE_PLAYING) { 571 mAudioManager.setParameters("A2dpSuspended=false"); 572 } else { 573 result = false; 574 } 575 } 576 return result; 577 } 578 579 /** 580 * Called by native code for the async response to a Connect 581 * method call to org.bluez.AudioSink. 582 * 583 * @param deviceObjectPath the object path for the connecting device 584 * @param result true on success; false on error 585 */ 586 private void onConnectSinkResult(String deviceObjectPath, boolean result) { 587 // If the call was a success, ignore we will update the state 588 // when we a Sink Property Change 589 if (!result) { 590 if (deviceObjectPath != null) { 591 String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); 592 if (address == null) return; 593 BluetoothDevice device = mAdapter.getRemoteDevice(address); 594 int state = getConnectionState(device); 595 handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED); 596 } 597 } 598 } 599 600 /** Handles A2DP connection state change intent broadcasts. */ 601 private class IntentBroadcastHandler extends Handler { 602 603 private void onConnectionStateChanged(BluetoothDevice device, int prevState, int state) { 604 Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 605 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 606 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); 607 intent.putExtra(BluetoothProfile.EXTRA_STATE, state); 608 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 609 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 610 611 if (DBG) log("A2DP state : device: " + device + " State:" + prevState + "->" + state); 612 613 mBluetoothService.sendConnectionStateChange(device, BluetoothProfile.A2DP, state, 614 prevState); 615 } 616 617 @Override 618 public void handleMessage(Message msg) { 619 switch (msg.what) { 620 case MSG_CONNECTION_STATE_CHANGED: 621 onConnectionStateChanged((BluetoothDevice) msg.obj, msg.arg1, msg.arg2); 622 mWakeLock.release(); 623 break; 624 } 625 } 626 } 627 628 @Override 629 protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 630 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); 631 632 if (mAudioDevices.isEmpty()) return; 633 pw.println("Cached audio devices:"); 634 for (BluetoothDevice device : mAudioDevices.keySet()) { 635 int state = mAudioDevices.get(device); 636 pw.println(device + " " + BluetoothA2dp.stateToString(state)); 637 } 638 } 639 640 private static void log(String msg) { 641 Log.d(TAG, msg); 642 } 643 644 private native boolean initNative(); 645 private native void cleanupNative(); 646 private synchronized native boolean connectSinkNative(String path); 647 private synchronized native boolean disconnectSinkNative(String path); 648 private synchronized native boolean suspendSinkNative(String path); 649 private synchronized native boolean resumeSinkNative(String path); 650 private synchronized native Object []getSinkPropertiesNative(String path); 651 private synchronized native boolean avrcpVolumeUpNative(String path); 652 private synchronized native boolean avrcpVolumeDownNative(String path); 653} 654