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