Home | History | Annotate | Download | only in server
      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 
     23 package android.server;
     24 
     25 import android.bluetooth.BluetoothA2dp;
     26 import android.bluetooth.BluetoothAdapter;
     27 import android.bluetooth.BluetoothDevice;
     28 import android.bluetooth.BluetoothUuid;
     29 import android.bluetooth.IBluetoothA2dp;
     30 import android.content.BroadcastReceiver;
     31 import android.content.Context;
     32 import android.content.Intent;
     33 import android.content.IntentFilter;
     34 import android.media.AudioManager;
     35 import android.os.Handler;
     36 import android.os.Message;
     37 import android.os.ParcelUuid;
     38 import android.provider.Settings;
     39 import android.util.Log;
     40 
     41 import java.io.FileDescriptor;
     42 import java.io.PrintWriter;
     43 import java.util.HashMap;
     44 import java.util.HashSet;
     45 import java.util.Set;
     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 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