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