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