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