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