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