• 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.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     /**
94      * A2DP sink device is streaming music. This state can be one of
95      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
96      * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
97      */
98     public static final int STATE_PLAYING   =  10;
99 
100     /**
101      * A2DP sink device is NOT streaming music. This state can be one of
102      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
103      * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
104      */
105     public static final int STATE_NOT_PLAYING   =  11;
106 
107     private Context mContext;
108     private ServiceListener mServiceListener;
109     private IBluetoothA2dp mService;
110     private BluetoothAdapter mAdapter;
111 
112     final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
113             new IBluetoothStateChangeCallback.Stub() {
114                 public void onBluetoothStateChange(boolean up) {
115                     if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
116                     if (!up) {
117                         if (VDBG) Log.d(TAG,"Unbinding service...");
118                         synchronized (mConnection) {
119                             try {
120                                 mService = null;
121                                 mContext.unbindService(mConnection);
122                             } catch (Exception re) {
123                                 Log.e(TAG,"",re);
124                             }
125                         }
126                     } else {
127                         synchronized (mConnection) {
128                             try {
129                                 if (mService == null) {
130                                     if (VDBG) Log.d(TAG,"Binding service...");
131                                     if (!mContext.bindService(new Intent(IBluetoothA2dp.class.getName()), mConnection, 0)) {
132                                         Log.e(TAG, "Could not bind to Bluetooth A2DP Service");
133                                     }
134                                 }
135                             } catch (Exception re) {
136                                 Log.e(TAG,"",re);
137                             }
138                         }
139                     }
140                 }
141         };
142     /**
143      * Create a BluetoothA2dp proxy object for interacting with the local
144      * Bluetooth A2DP service.
145      *
146      */
BluetoothA2dp(Context context, ServiceListener l)147     /*package*/ BluetoothA2dp(Context context, ServiceListener l) {
148         mContext = context;
149         mServiceListener = l;
150         mAdapter = BluetoothAdapter.getDefaultAdapter();
151         IBluetoothManager mgr = mAdapter.getBluetoothManager();
152         if (mgr != null) {
153             try {
154                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
155             } catch (RemoteException e) {
156                 Log.e(TAG,"",e);
157             }
158         }
159 
160         if (!context.bindService(new Intent(IBluetoothA2dp.class.getName()), mConnection, 0)) {
161             Log.e(TAG, "Could not bind to Bluetooth A2DP Service");
162         }
163     }
164 
close()165     /*package*/ void close() {
166         mServiceListener = null;
167         IBluetoothManager mgr = mAdapter.getBluetoothManager();
168         if (mgr != null) {
169             try {
170                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
171             } catch (Exception e) {
172                 Log.e(TAG,"",e);
173             }
174         }
175 
176         synchronized (mConnection) {
177             if (mService != null) {
178                 try {
179                     mService = null;
180                     mContext.unbindService(mConnection);
181                 } catch (Exception re) {
182                     Log.e(TAG,"",re);
183                 }
184             }
185         }
186     }
187 
finalize()188     public void finalize() {
189         close();
190     }
191     /**
192      * Initiate connection to a profile of the remote bluetooth device.
193      *
194      * <p> Currently, the system supports only 1 connection to the
195      * A2DP profile. The API will automatically disconnect connected
196      * devices before connecting.
197      *
198      * <p> This API returns false in scenarios like the profile on the
199      * device is already connected or Bluetooth is not turned on.
200      * When this API returns true, it is guaranteed that
201      * connection state intent for the profile will be broadcasted with
202      * the state. Users can get the connection state of the profile
203      * from this intent.
204      *
205      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
206      * permission.
207      *
208      * @param device Remote Bluetooth Device
209      * @return false on immediate error,
210      *               true otherwise
211      * @hide
212      */
connect(BluetoothDevice device)213     public boolean connect(BluetoothDevice device) {
214         if (DBG) log("connect(" + device + ")");
215         if (mService != null && isEnabled() &&
216             isValidDevice(device)) {
217             try {
218                 return mService.connect(device);
219             } catch (RemoteException e) {
220                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
221                 return false;
222             }
223         }
224         if (mService == null) Log.w(TAG, "Proxy not attached to service");
225         return false;
226     }
227 
228     /**
229      * Initiate disconnection from a profile
230      *
231      * <p> This API will return false in scenarios like the profile on the
232      * Bluetooth device is not in connected state etc. When this API returns,
233      * true, it is guaranteed that the connection state change
234      * intent will be broadcasted with the state. Users can get the
235      * disconnection state of the profile from this intent.
236      *
237      * <p> If the disconnection is initiated by a remote device, the state
238      * will transition from {@link #STATE_CONNECTED} to
239      * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
240      * host (local) device the state will transition from
241      * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
242      * state {@link #STATE_DISCONNECTED}. The transition to
243      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
244      * two scenarios.
245      *
246      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
247      * permission.
248      *
249      * @param device Remote Bluetooth Device
250      * @return false on immediate error,
251      *               true otherwise
252      * @hide
253      */
disconnect(BluetoothDevice device)254     public boolean disconnect(BluetoothDevice device) {
255         if (DBG) log("disconnect(" + device + ")");
256         if (mService != null && isEnabled() &&
257             isValidDevice(device)) {
258             try {
259                 return mService.disconnect(device);
260             } catch (RemoteException e) {
261                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
262                 return false;
263             }
264         }
265         if (mService == null) Log.w(TAG, "Proxy not attached to service");
266         return false;
267     }
268 
269     /**
270      * {@inheritDoc}
271      */
getConnectedDevices()272     public List<BluetoothDevice> getConnectedDevices() {
273         if (VDBG) log("getConnectedDevices()");
274         if (mService != null && isEnabled()) {
275             try {
276                 return mService.getConnectedDevices();
277             } catch (RemoteException e) {
278                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
279                 return new ArrayList<BluetoothDevice>();
280             }
281         }
282         if (mService == null) Log.w(TAG, "Proxy not attached to service");
283         return new ArrayList<BluetoothDevice>();
284     }
285 
286     /**
287      * {@inheritDoc}
288      */
getDevicesMatchingConnectionStates(int[] states)289     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
290         if (VDBG) log("getDevicesMatchingStates()");
291         if (mService != null && isEnabled()) {
292             try {
293                 return mService.getDevicesMatchingConnectionStates(states);
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      */
getConnectionState(BluetoothDevice device)306     public int getConnectionState(BluetoothDevice device) {
307         if (VDBG) log("getState(" + device + ")");
308         if (mService != null && isEnabled()
309             && isValidDevice(device)) {
310             try {
311                 return mService.getConnectionState(device);
312             } catch (RemoteException e) {
313                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
314                 return BluetoothProfile.STATE_DISCONNECTED;
315             }
316         }
317         if (mService == null) Log.w(TAG, "Proxy not attached to service");
318         return BluetoothProfile.STATE_DISCONNECTED;
319     }
320 
321     /**
322      * Set priority of the profile
323      *
324      * <p> The device should already be paired.
325      *  Priority can be one of {@link #PRIORITY_ON} orgetBluetoothManager
326      * {@link #PRIORITY_OFF},
327      *
328      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
329      * permission.
330      *
331      * @param device Paired bluetooth device
332      * @param priority
333      * @return true if priority is set, false on error
334      * @hide
335      */
setPriority(BluetoothDevice device, int priority)336     public boolean setPriority(BluetoothDevice device, int priority) {
337         if (DBG) log("setPriority(" + device + ", " + priority + ")");
338         if (mService != null && isEnabled()
339             && isValidDevice(device)) {
340             if (priority != BluetoothProfile.PRIORITY_OFF &&
341                 priority != BluetoothProfile.PRIORITY_ON){
342               return false;
343             }
344             try {
345                 return mService.setPriority(device, priority);
346             } catch (RemoteException e) {
347                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
348                 return false;
349             }
350         }
351         if (mService == null) Log.w(TAG, "Proxy not attached to service");
352         return false;
353     }
354 
355     /**
356      * Get the priority of the profile.
357      *
358      * <p> The priority can be any of:
359      * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
360      * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
361      *
362      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
363      *
364      * @param device Bluetooth device
365      * @return priority of the device
366      * @hide
367      */
getPriority(BluetoothDevice device)368     public int getPriority(BluetoothDevice device) {
369         if (VDBG) log("getPriority(" + device + ")");
370         if (mService != null && isEnabled()
371             && isValidDevice(device)) {
372             try {
373                 return mService.getPriority(device);
374             } catch (RemoteException e) {
375                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
376                 return BluetoothProfile.PRIORITY_OFF;
377             }
378         }
379         if (mService == null) Log.w(TAG, "Proxy not attached to service");
380         return BluetoothProfile.PRIORITY_OFF;
381     }
382 
383     /**
384      * Check if A2DP profile is streaming music.
385      *
386      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
387      *
388      * @param device BluetoothDevice device
389      */
isA2dpPlaying(BluetoothDevice device)390     public boolean isA2dpPlaying(BluetoothDevice device) {
391         if (mService != null && isEnabled()
392             && isValidDevice(device)) {
393             try {
394                 return mService.isA2dpPlaying(device);
395             } catch (RemoteException e) {
396                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
397                 return false;
398             }
399         }
400         if (mService == null) Log.w(TAG, "Proxy not attached to service");
401         return false;
402     }
403 
404     /**
405      * This function checks if the remote device is an AVCRP
406      * target and thus whether we should send volume keys
407      * changes or not.
408      * @hide
409      */
shouldSendVolumeKeys(BluetoothDevice device)410     public boolean shouldSendVolumeKeys(BluetoothDevice device) {
411         if (isEnabled() && isValidDevice(device)) {
412             ParcelUuid[] uuids = device.getUuids();
413             if (uuids == null) return false;
414 
415             for (ParcelUuid uuid: uuids) {
416                 if (BluetoothUuid.isAvrcpTarget(uuid)) {
417                     return true;
418                 }
419             }
420         }
421         return false;
422     }
423 
424     /**
425      * Helper for converting a state to a string.
426      *
427      * For debug use only - strings are not internationalized.
428      * @hide
429      */
stateToString(int state)430     public static String stateToString(int state) {
431         switch (state) {
432         case STATE_DISCONNECTED:
433             return "disconnected";
434         case STATE_CONNECTING:
435             return "connecting";
436         case STATE_CONNECTED:
437             return "connected";
438         case STATE_DISCONNECTING:
439             return "disconnecting";
440         case STATE_PLAYING:
441             return "playing";
442         case STATE_NOT_PLAYING:
443           return "not playing";
444         default:
445             return "<unknown state " + state + ">";
446         }
447     }
448 
449     private ServiceConnection mConnection = new ServiceConnection() {
450         public void onServiceConnected(ComponentName className, IBinder service) {
451             if (DBG) Log.d(TAG, "Proxy object connected");
452             mService = IBluetoothA2dp.Stub.asInterface(service);
453 
454             if (mServiceListener != null) {
455                 mServiceListener.onServiceConnected(BluetoothProfile.A2DP, BluetoothA2dp.this);
456             }
457         }
458         public void onServiceDisconnected(ComponentName className) {
459             if (DBG) Log.d(TAG, "Proxy object disconnected");
460             mService = null;
461             if (mServiceListener != null) {
462                 mServiceListener.onServiceDisconnected(BluetoothProfile.A2DP);
463             }
464         }
465     };
466 
isEnabled()467     private boolean isEnabled() {
468        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
469        return false;
470     }
471 
isValidDevice(BluetoothDevice device)472     private boolean isValidDevice(BluetoothDevice device) {
473        if (device == null) return false;
474 
475        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
476        return false;
477     }
478 
log(String msg)479     private static void log(String msg) {
480       Log.d(TAG, msg);
481     }
482 }
483