• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
isPhoneDocked(BluetoothDevice device)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 
BluetoothA2dpService(Context context, BluetoothService bluetoothService)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
finalize()182     protected void finalize() throws Throwable {
183         try {
184             cleanupNative();
185         } finally {
186             super.finalize();
187         }
188     }
189 
convertBluezSinkStringtoState(String value)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 
isSinkDevice(BluetoothDevice device)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 
addAudioSink(BluetoothDevice device)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 
onBluetoothEnable()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 
onBluetoothDisable()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 
isConnectSinkFeasible(BluetoothDevice device)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 
connectSink(BluetoothDevice device)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 
connectSinkInternal(BluetoothDevice device)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 
isDisconnectSinkFeasible(BluetoothDevice device)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 
disconnectSink(BluetoothDevice device)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 
disconnectSinkInternal(BluetoothDevice device)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 
suspendSink(BluetoothDevice device)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 
resumeSink(BluetoothDevice device)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 
getConnectedSinks()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 
getNonDisconnectedSinks()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 
getSinkState(BluetoothDevice device)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 
getSinkPriority(BluetoothDevice device)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 
setSinkPriority(BluetoothDevice device, int priority)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 
allowIncomingConnect(BluetoothDevice device, boolean value)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 
onSinkPropertyChanged(String path, String []propValues)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 
handleSinkStateChange(BluetoothDevice device, int prevState, int state)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 
adjustOtherSinkPriorities(BluetoothDevice connectedDevice)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 
lookupSinksMatchingStates(int[] states)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 
checkSinkSuspendState(int state)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 
onConnectSinkResult(String deviceObjectPath, boolean result)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
dump(FileDescriptor fd, PrintWriter pw, String[] args)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 
log(String msg)603     private static void log(String msg) {
604         Log.d(TAG, msg);
605     }
606 
initNative()607     private native boolean initNative();
cleanupNative()608     private native void cleanupNative();
connectSinkNative(String path)609     private synchronized native boolean connectSinkNative(String path);
disconnectSinkNative(String path)610     private synchronized native boolean disconnectSinkNative(String path);
suspendSinkNative(String path)611     private synchronized native boolean suspendSinkNative(String path);
resumeSinkNative(String path)612     private synchronized native boolean resumeSinkNative(String path);
getSinkPropertiesNative(String path)613     private synchronized native Object []getSinkPropertiesNative(String path);
avrcpVolumeUpNative(String path)614     private synchronized native boolean avrcpVolumeUpNative(String path);
avrcpVolumeDownNative(String path)615     private synchronized native boolean avrcpVolumeDownNative(String path);
616 }
617