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     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             if (mAudioDevices.get(device) == null) {
    477                 // This is for an incoming connection for a device not known to us.
    478                 // We have authorized it and bluez state has changed.
    479                 addAudioSink(device);
    480             } else {
    481                 int prevState = mAudioDevices.get(device);
    482                 handleSinkStateChange(device, prevState, state);
    483             }
    484         }
    485     }
    486 
    487     private void handleSinkStateChange(BluetoothDevice device, int prevState, int state) {
    488         if (state != prevState) {
    489             if (state == BluetoothA2dp.STATE_DISCONNECTED ||
    490                     state == BluetoothA2dp.STATE_DISCONNECTING) {
    491                 mSinkCount--;
    492             } else if (state == BluetoothA2dp.STATE_CONNECTED) {
    493                 mSinkCount ++;
    494             }
    495             mAudioDevices.put(device, state);
    496 
    497             checkSinkSuspendState(state);
    498             mTargetA2dpState = -1;
    499 
    500             if (getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF &&
    501                     state == BluetoothA2dp.STATE_CONNECTED) {
    502                 // We have connected or attempting to connect.
    503                 // Bump priority
    504                 setSinkPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT);
    505                 // We will only have 1 device with AUTO_CONNECT priority
    506                 // To be backward compatible set everyone else to have PRIORITY_ON
    507                 adjustOtherSinkPriorities(device);
    508             }
    509 
    510             Intent intent = new Intent(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
    511             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
    512             intent.putExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, prevState);
    513             intent.putExtra(BluetoothA2dp.EXTRA_SINK_STATE, state);
    514             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    515 
    516             if (DBG) log("A2DP state : device: " + device + " State:" + prevState + "->" + state);
    517         }
    518     }
    519 
    520     private void adjustOtherSinkPriorities(BluetoothDevice connectedDevice) {
    521         for (BluetoothDevice device : mAdapter.getBondedDevices()) {
    522             if (getSinkPriority(device) >= BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
    523                 !device.equals(connectedDevice)) {
    524                 setSinkPriority(device, BluetoothA2dp.PRIORITY_ON);
    525             }
    526         }
    527     }
    528 
    529     private synchronized Set<BluetoothDevice> lookupSinksMatchingStates(int[] states) {
    530         Set<BluetoothDevice> sinks = new HashSet<BluetoothDevice>();
    531         if (mAudioDevices.isEmpty()) {
    532             return sinks;
    533         }
    534         for (BluetoothDevice device: mAudioDevices.keySet()) {
    535             int sinkState = getSinkState(device);
    536             for (int state : states) {
    537                 if (state == sinkState) {
    538                     sinks.add(device);
    539                     break;
    540                 }
    541             }
    542         }
    543         return sinks;
    544     }
    545 
    546     private boolean checkSinkSuspendState(int state) {
    547         boolean result = true;
    548 
    549         if (state != mTargetA2dpState) {
    550             if (state == BluetoothA2dp.STATE_PLAYING &&
    551                 mTargetA2dpState == BluetoothA2dp.STATE_CONNECTED) {
    552                 mAudioManager.setParameters("A2dpSuspended=true");
    553             } else if (state == BluetoothA2dp.STATE_CONNECTED &&
    554                 mTargetA2dpState == BluetoothA2dp.STATE_PLAYING) {
    555                 mAudioManager.setParameters("A2dpSuspended=false");
    556             } else {
    557                 result = false;
    558             }
    559         }
    560         return result;
    561     }
    562 
    563     private void onConnectSinkResult(String deviceObjectPath, boolean result) {
    564         // If the call was a success, ignore we will update the state
    565         // when we a Sink Property Change
    566         if (!result) {
    567             if (deviceObjectPath != null) {
    568                 String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
    569                 if (address == null) return;
    570                 BluetoothDevice device = mAdapter.getRemoteDevice(address);
    571                 int state = getSinkState(device);
    572                 handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
    573             }
    574         }
    575     }
    576 
    577     @Override
    578     protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    579         if (mAudioDevices.isEmpty()) return;
    580         pw.println("Cached audio devices:");
    581         for (BluetoothDevice device : mAudioDevices.keySet()) {
    582             int state = mAudioDevices.get(device);
    583             pw.println(device + " " + BluetoothA2dp.stateToString(state));
    584         }
    585     }
    586 
    587     private static void log(String msg) {
    588         Log.d(TAG, msg);
    589     }
    590 
    591     private native boolean initNative();
    592     private native void cleanupNative();
    593     private synchronized native boolean connectSinkNative(String path);
    594     private synchronized native boolean disconnectSinkNative(String path);
    595     private synchronized native boolean suspendSinkNative(String path);
    596     private synchronized native boolean resumeSinkNative(String path);
    597     private synchronized native Object []getSinkPropertiesNative(String path);
    598     private synchronized native boolean avrcpVolumeUpNative(String path);
    599     private synchronized native boolean avrcpVolumeDownNative(String path);
    600 }
    601