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