• 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 package android.bluetooth;
18 
19 import android.Manifest;
20 import android.annotation.RequiresPermission;
21 import android.annotation.SdkConstant;
22 import android.annotation.SdkConstant.SdkConstantType;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.ServiceConnection;
27 import android.media.AudioManager;
28 import android.os.IBinder;
29 import android.os.ParcelUuid;
30 import android.os.RemoteException;
31 import android.util.Log;
32 
33 import com.android.internal.annotations.GuardedBy;
34 
35 import java.util.ArrayList;
36 import java.util.List;
37 import java.util.concurrent.locks.ReentrantReadWriteLock;
38 
39 
40 /**
41  * This class provides the public APIs to control the Bluetooth A2DP
42  * profile.
43  *
44  *<p>BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP
45  * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
46  * the BluetoothA2dp proxy object.
47  *
48  * <p> Android only supports one connected Bluetooth A2dp device at a time.
49  * Each method is protected with its appropriate permission.
50  */
51 public final class BluetoothA2dp implements BluetoothProfile {
52     private static final String TAG = "BluetoothA2dp";
53     private static final boolean DBG = true;
54     private static final boolean VDBG = false;
55 
56     /**
57      * Intent used to broadcast the change in connection state of the A2DP
58      * profile.
59      *
60      * <p>This intent will have 3 extras:
61      * <ul>
62      *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
63      *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
64      *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
65      * </ul>
66      *
67      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
68      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
69      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
70      *
71      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
72      * receive.
73      */
74     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
75     public static final String ACTION_CONNECTION_STATE_CHANGED =
76         "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED";
77 
78     /**
79      * Intent used to broadcast the change in the Playing state of the A2DP
80      * profile.
81      *
82      * <p>This intent will have 3 extras:
83      * <ul>
84      *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
85      *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
86      *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
87      * </ul>
88      *
89      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
90      * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING},
91      *
92      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
93      * receive.
94      */
95     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
96     public static final String ACTION_PLAYING_STATE_CHANGED =
97         "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED";
98 
99     /** @hide */
100     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
101     public static final String ACTION_AVRCP_CONNECTION_STATE_CHANGED =
102         "android.bluetooth.a2dp.profile.action.AVRCP_CONNECTION_STATE_CHANGED";
103 
104     /**
105      * A2DP sink device is streaming music. This state can be one of
106      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
107      * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
108      */
109     public static final int STATE_PLAYING   =  10;
110 
111     /**
112      * A2DP sink device is NOT streaming music. This state can be one of
113      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
114      * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
115      */
116     public static final int STATE_NOT_PLAYING   =  11;
117 
118     private Context mContext;
119     private ServiceListener mServiceListener;
120     private final ReentrantReadWriteLock mServiceLock = new ReentrantReadWriteLock();
121     @GuardedBy("mServiceLock") private IBluetoothA2dp mService;
122     private BluetoothAdapter mAdapter;
123 
124     final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
125             new IBluetoothStateChangeCallback.Stub() {
126                 public void onBluetoothStateChange(boolean up) {
127                     if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
128                     if (!up) {
129                         if (VDBG) Log.d(TAG, "Unbinding service...");
130                         try {
131                             mServiceLock.writeLock().lock();
132                             mService = null;
133                             mContext.unbindService(mConnection);
134                         } catch (Exception re) {
135                             Log.e(TAG, "", re);
136                         } finally {
137                             mServiceLock.writeLock().unlock();
138                         }
139                     } else {
140                         try {
141                             mServiceLock.readLock().lock();
142                             if (mService == null) {
143                                 if (VDBG) Log.d(TAG,"Binding service...");
144                                 doBind();
145                             }
146                         } catch (Exception re) {
147                             Log.e(TAG,"",re);
148                         } finally {
149                             mServiceLock.readLock().unlock();
150                         }
151                     }
152                 }
153         };
154     /**
155      * Create a BluetoothA2dp proxy object for interacting with the local
156      * Bluetooth A2DP service.
157      *
158      */
BluetoothA2dp(Context context, ServiceListener l)159     /*package*/ BluetoothA2dp(Context context, ServiceListener l) {
160         mContext = context;
161         mServiceListener = l;
162         mAdapter = BluetoothAdapter.getDefaultAdapter();
163         IBluetoothManager mgr = mAdapter.getBluetoothManager();
164         if (mgr != null) {
165             try {
166                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
167             } catch (RemoteException e) {
168                 Log.e(TAG,"",e);
169             }
170         }
171 
172         doBind();
173     }
174 
doBind()175     boolean doBind() {
176         Intent intent = new Intent(IBluetoothA2dp.class.getName());
177         ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
178         intent.setComponent(comp);
179         if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
180                 android.os.Process.myUserHandle())) {
181             Log.e(TAG, "Could not bind to Bluetooth A2DP Service with " + intent);
182             return false;
183         }
184         return true;
185     }
186 
close()187     /*package*/ void close() {
188         mServiceListener = null;
189         IBluetoothManager mgr = mAdapter.getBluetoothManager();
190         if (mgr != null) {
191             try {
192                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
193             } catch (Exception e) {
194                 Log.e(TAG,"",e);
195             }
196         }
197 
198         try {
199             mServiceLock.writeLock().lock();
200             if (mService != null) {
201                 mService = null;
202                 mContext.unbindService(mConnection);
203             }
204         } catch (Exception re) {
205             Log.e(TAG, "", re);
206         } finally {
207             mServiceLock.writeLock().unlock();
208         }
209     }
210 
finalize()211     public void finalize() {
212         // The empty finalize needs to be kept or the
213         // cts signature tests would fail.
214     }
215     /**
216      * Initiate connection to a profile of the remote bluetooth device.
217      *
218      * <p> Currently, the system supports only 1 connection to the
219      * A2DP profile. The API will automatically disconnect connected
220      * devices before connecting.
221      *
222      * <p> This API returns false in scenarios like the profile on the
223      * device is already connected or Bluetooth is not turned on.
224      * When this API returns true, it is guaranteed that
225      * connection state intent for the profile will be broadcasted with
226      * the state. Users can get the connection state of the profile
227      * from this intent.
228      *
229      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
230      * permission.
231      *
232      * @param device Remote Bluetooth Device
233      * @return false on immediate error,
234      *               true otherwise
235      * @hide
236      */
connect(BluetoothDevice device)237     public boolean connect(BluetoothDevice device) {
238         if (DBG) log("connect(" + device + ")");
239         try {
240             mServiceLock.readLock().lock();
241             if (mService != null && isEnabled() &&
242                 isValidDevice(device)) {
243                 return mService.connect(device);
244             }
245             if (mService == null) Log.w(TAG, "Proxy not attached to service");
246             return false;
247         } catch (RemoteException e) {
248             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
249             return false;
250         } finally {
251             mServiceLock.readLock().unlock();
252         }
253     }
254 
255     /**
256      * Initiate disconnection from a profile
257      *
258      * <p> This API will return false in scenarios like the profile on the
259      * Bluetooth device is not in connected state etc. When this API returns,
260      * true, it is guaranteed that the connection state change
261      * intent will be broadcasted with the state. Users can get the
262      * disconnection state of the profile from this intent.
263      *
264      * <p> If the disconnection is initiated by a remote device, the state
265      * will transition from {@link #STATE_CONNECTED} to
266      * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
267      * host (local) device the state will transition from
268      * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
269      * state {@link #STATE_DISCONNECTED}. The transition to
270      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
271      * two scenarios.
272      *
273      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
274      * permission.
275      *
276      * @param device Remote Bluetooth Device
277      * @return false on immediate error,
278      *               true otherwise
279      * @hide
280      */
disconnect(BluetoothDevice device)281     public boolean disconnect(BluetoothDevice device) {
282         if (DBG) log("disconnect(" + device + ")");
283         try {
284             mServiceLock.readLock().lock();
285             if (mService != null && isEnabled() &&
286                 isValidDevice(device)) {
287                 return mService.disconnect(device);
288             }
289             if (mService == null) Log.w(TAG, "Proxy not attached to service");
290             return false;
291         } catch (RemoteException e) {
292             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
293             return false;
294         } finally {
295             mServiceLock.readLock().unlock();
296         }
297     }
298 
299     /**
300      * {@inheritDoc}
301      */
getConnectedDevices()302     public List<BluetoothDevice> getConnectedDevices() {
303         if (VDBG) log("getConnectedDevices()");
304         try {
305             mServiceLock.readLock().lock();
306             if (mService != null && isEnabled()) {
307                 return mService.getConnectedDevices();
308             }
309             if (mService == null) Log.w(TAG, "Proxy not attached to service");
310             return new ArrayList<BluetoothDevice>();
311         } catch (RemoteException e) {
312             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
313             return new ArrayList<BluetoothDevice>();
314         } finally {
315             mServiceLock.readLock().unlock();
316         }
317     }
318 
319     /**
320      * {@inheritDoc}
321      */
getDevicesMatchingConnectionStates(int[] states)322     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
323         if (VDBG) log("getDevicesMatchingStates()");
324         try {
325             mServiceLock.readLock().lock();
326             if (mService != null && isEnabled()) {
327                 return mService.getDevicesMatchingConnectionStates(states);
328             }
329             if (mService == null) Log.w(TAG, "Proxy not attached to service");
330             return new ArrayList<BluetoothDevice>();
331         } catch (RemoteException e) {
332             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
333             return new ArrayList<BluetoothDevice>();
334         } finally {
335             mServiceLock.readLock().unlock();
336         }
337     }
338 
339     /**
340      * {@inheritDoc}
341      */
getConnectionState(BluetoothDevice device)342     public int getConnectionState(BluetoothDevice device) {
343         if (VDBG) log("getState(" + device + ")");
344         try {
345             mServiceLock.readLock().lock();
346             if (mService != null && isEnabled()
347                 && isValidDevice(device)) {
348                 return mService.getConnectionState(device);
349             }
350             if (mService == null) Log.w(TAG, "Proxy not attached to service");
351             return BluetoothProfile.STATE_DISCONNECTED;
352         } catch (RemoteException e) {
353             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
354             return BluetoothProfile.STATE_DISCONNECTED;
355         } finally {
356             mServiceLock.readLock().unlock();
357         }
358     }
359 
360     /**
361      * Set priority of the profile
362      *
363      * <p> The device should already be paired.
364      *  Priority can be one of {@link #PRIORITY_ON} orgetBluetoothManager
365      * {@link #PRIORITY_OFF},
366      *
367      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
368      * permission.
369      *
370      * @param device Paired bluetooth device
371      * @param priority
372      * @return true if priority is set, false on error
373      * @hide
374      */
setPriority(BluetoothDevice device, int priority)375     public boolean setPriority(BluetoothDevice device, int priority) {
376         if (DBG) log("setPriority(" + device + ", " + priority + ")");
377         try {
378             mServiceLock.readLock().lock();
379             if (mService != null && isEnabled()
380                 && isValidDevice(device)) {
381                 if (priority != BluetoothProfile.PRIORITY_OFF &&
382                     priority != BluetoothProfile.PRIORITY_ON) {
383                     return false;
384                 }
385                 return mService.setPriority(device, priority);
386             }
387             if (mService == null) Log.w(TAG, "Proxy not attached to service");
388             return false;
389         } catch (RemoteException e) {
390             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
391             return false;
392         } finally {
393             mServiceLock.readLock().unlock();
394         }
395     }
396 
397     /**
398      * Get the priority of the profile.
399      *
400      * <p> The priority can be any of:
401      * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
402      * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
403      *
404      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
405      *
406      * @param device Bluetooth device
407      * @return priority of the device
408      * @hide
409      */
410     @RequiresPermission(Manifest.permission.BLUETOOTH)
getPriority(BluetoothDevice device)411     public int getPriority(BluetoothDevice device) {
412         if (VDBG) log("getPriority(" + device + ")");
413         try {
414             mServiceLock.readLock().lock();
415             if (mService != null && isEnabled()
416                 && isValidDevice(device)) {
417                 return mService.getPriority(device);
418             }
419             if (mService == null) Log.w(TAG, "Proxy not attached to service");
420             return BluetoothProfile.PRIORITY_OFF;
421         } catch (RemoteException e) {
422             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
423             return BluetoothProfile.PRIORITY_OFF;
424         } finally {
425             mServiceLock.readLock().unlock();
426         }
427     }
428 
429     /**
430      * Checks if Avrcp device supports the absolute volume feature.
431      *
432      * @return true if device supports absolute volume
433      * @hide
434      */
isAvrcpAbsoluteVolumeSupported()435     public boolean isAvrcpAbsoluteVolumeSupported() {
436         if (DBG) Log.d(TAG, "isAvrcpAbsoluteVolumeSupported");
437         try {
438             mServiceLock.readLock().lock();
439             if (mService != null && isEnabled()) {
440                 return mService.isAvrcpAbsoluteVolumeSupported();
441             }
442             if (mService == null) Log.w(TAG, "Proxy not attached to service");
443             return false;
444         } catch (RemoteException e) {
445             Log.e(TAG, "Error talking to BT service in isAvrcpAbsoluteVolumeSupported()", e);
446             return false;
447         } finally {
448             mServiceLock.readLock().unlock();
449         }
450     }
451 
452     /**
453      * Tells remote device to adjust volume. Only if absolute volume is
454      * supported. Uses the following values:
455      * <ul>
456      * <li>{@link AudioManager#ADJUST_LOWER}</li>
457      * <li>{@link AudioManager#ADJUST_RAISE}</li>
458      * <li>{@link AudioManager#ADJUST_MUTE}</li>
459      * <li>{@link AudioManager#ADJUST_UNMUTE}</li>
460      * </ul>
461      *
462      * @param direction One of the supported adjust values.
463      * @hide
464      */
adjustAvrcpAbsoluteVolume(int direction)465     public void adjustAvrcpAbsoluteVolume(int direction) {
466         if (DBG) Log.d(TAG, "adjustAvrcpAbsoluteVolume");
467         try {
468             mServiceLock.readLock().lock();
469             if (mService != null && isEnabled()) {
470                 mService.adjustAvrcpAbsoluteVolume(direction);
471             }
472             if (mService == null) Log.w(TAG, "Proxy not attached to service");
473         } catch (RemoteException e) {
474             Log.e(TAG, "Error talking to BT service in adjustAvrcpAbsoluteVolume()", e);
475         } finally {
476             mServiceLock.readLock().unlock();
477         }
478     }
479 
480     /**
481      * Tells remote device to set an absolute volume. Only if absolute volume is supported
482      *
483      * @param volume Absolute volume to be set on AVRCP side
484      * @hide
485      */
setAvrcpAbsoluteVolume(int volume)486     public void setAvrcpAbsoluteVolume(int volume) {
487         if (DBG) Log.d(TAG, "setAvrcpAbsoluteVolume");
488         try {
489             mServiceLock.readLock().lock();
490             if (mService != null && isEnabled()) {
491                 mService.setAvrcpAbsoluteVolume(volume);
492             }
493             if (mService == null) Log.w(TAG, "Proxy not attached to service");
494         } catch (RemoteException e) {
495             Log.e(TAG, "Error talking to BT service in setAvrcpAbsoluteVolume()", e);
496         } finally {
497             mServiceLock.readLock().unlock();
498         }
499     }
500 
501     /**
502      * Check if A2DP profile is streaming music.
503      *
504      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
505      *
506      * @param device BluetoothDevice device
507      */
isA2dpPlaying(BluetoothDevice device)508     public boolean isA2dpPlaying(BluetoothDevice device) {
509         try {
510             mServiceLock.readLock().lock();
511             if (mService != null && isEnabled()
512                 && isValidDevice(device)) {
513                 return mService.isA2dpPlaying(device);
514             }
515             if (mService == null) Log.w(TAG, "Proxy not attached to service");
516             return false;
517         } catch (RemoteException e) {
518             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
519             return false;
520         } finally {
521             mServiceLock.readLock().unlock();
522         }
523     }
524 
525     /**
526      * This function checks if the remote device is an AVCRP
527      * target and thus whether we should send volume keys
528      * changes or not.
529      * @hide
530      */
shouldSendVolumeKeys(BluetoothDevice device)531     public boolean shouldSendVolumeKeys(BluetoothDevice device) {
532         if (isEnabled() && isValidDevice(device)) {
533             ParcelUuid[] uuids = device.getUuids();
534             if (uuids == null) return false;
535 
536             for (ParcelUuid uuid: uuids) {
537                 if (BluetoothUuid.isAvrcpTarget(uuid)) {
538                     return true;
539                 }
540             }
541         }
542         return false;
543     }
544 
545     /**
546      * Helper for converting a state to a string.
547      *
548      * For debug use only - strings are not internationalized.
549      * @hide
550      */
stateToString(int state)551     public static String stateToString(int state) {
552         switch (state) {
553         case STATE_DISCONNECTED:
554             return "disconnected";
555         case STATE_CONNECTING:
556             return "connecting";
557         case STATE_CONNECTED:
558             return "connected";
559         case STATE_DISCONNECTING:
560             return "disconnecting";
561         case STATE_PLAYING:
562             return "playing";
563         case STATE_NOT_PLAYING:
564           return "not playing";
565         default:
566             return "<unknown state " + state + ">";
567         }
568     }
569 
570     private final ServiceConnection mConnection = new ServiceConnection() {
571         public void onServiceConnected(ComponentName className, IBinder service) {
572             if (DBG) Log.d(TAG, "Proxy object connected");
573             try {
574                 mServiceLock.writeLock().lock();
575                 mService = IBluetoothA2dp.Stub.asInterface(service);
576             } finally {
577                 mServiceLock.writeLock().unlock();
578             }
579 
580             if (mServiceListener != null) {
581                 mServiceListener.onServiceConnected(BluetoothProfile.A2DP, BluetoothA2dp.this);
582             }
583         }
584         public void onServiceDisconnected(ComponentName className) {
585             if (DBG) Log.d(TAG, "Proxy object disconnected");
586             try {
587                 mServiceLock.writeLock().lock();
588                 mService = null;
589             } finally {
590                 mServiceLock.writeLock().unlock();
591             }
592             if (mServiceListener != null) {
593                 mServiceListener.onServiceDisconnected(BluetoothProfile.A2DP);
594             }
595         }
596     };
597 
isEnabled()598     private boolean isEnabled() {
599        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
600        return false;
601     }
602 
isValidDevice(BluetoothDevice device)603     private boolean isValidDevice(BluetoothDevice device) {
604        if (device == null) return false;
605 
606        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
607        return false;
608     }
609 
log(String msg)610     private static void log(String msg) {
611       Log.d(TAG, msg);
612     }
613 }
614