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