• 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     final private 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      * Create a BluetoothA2dp proxy object for interacting with the local
161      * Bluetooth A2DP service.
162      *
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                 android.os.Process.myUserHandle())) {
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 
finalize()215     public void finalize() {
216         close();
217     }
218     /**
219      * Initiate connection to a profile of the remote bluetooth device.
220      *
221      * <p> Currently, the system supports only 1 connection to the
222      * A2DP profile. The API will automatically disconnect connected
223      * devices before connecting.
224      *
225      * <p> This API returns false in scenarios like the profile on the
226      * device is already connected or Bluetooth is not turned on.
227      * When this API returns true, it is guaranteed that
228      * connection state intent for the profile will be broadcasted with
229      * the state. Users can get the connection state of the profile
230      * from this intent.
231      *
232      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
233      * permission.
234      *
235      * @param device Remote Bluetooth Device
236      * @return false on immediate error,
237      *               true otherwise
238      * @hide
239      */
connect(BluetoothDevice device)240     public boolean connect(BluetoothDevice device) {
241         if (DBG) log("connect(" + device + ")");
242         final IBluetoothA2dpSink service = mService;
243         if (service != null && isEnabled() && isValidDevice(device)) {
244             try {
245                 return service.connect(device);
246             } catch (RemoteException e) {
247                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
248                 return false;
249             }
250         }
251         if (service == null) Log.w(TAG, "Proxy not attached to service");
252         return false;
253     }
254 
255     /**
256      * Initiate disconnection from a profile
257      *
258      * <p> This API will return false in scenarios like the profile on the
259      * Bluetooth device is not in connected state etc. When this API returns,
260      * true, it is guaranteed that the connection state change
261      * intent will be broadcasted with the state. Users can get the
262      * disconnection state of the profile from this intent.
263      *
264      * <p> If the disconnection is initiated by a remote device, the state
265      * will transition from {@link #STATE_CONNECTED} to
266      * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
267      * host (local) device the state will transition from
268      * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
269      * state {@link #STATE_DISCONNECTED}. The transition to
270      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
271      * two scenarios.
272      *
273      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
274      * permission.
275      *
276      * @param device Remote Bluetooth Device
277      * @return false on immediate error,
278      *               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      */
getConnectedDevices()299     public List<BluetoothDevice> getConnectedDevices() {
300         if (VDBG) log("getConnectedDevices()");
301         final IBluetoothA2dpSink service = mService;
302         if (service != null && isEnabled()) {
303             try {
304                 return service.getConnectedDevices();
305             } catch (RemoteException e) {
306                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
307                 return new ArrayList<BluetoothDevice>();
308             }
309         }
310         if (service == null) Log.w(TAG, "Proxy not attached to service");
311         return new ArrayList<BluetoothDevice>();
312     }
313 
314     /**
315      * {@inheritDoc}
316      */
getDevicesMatchingConnectionStates(int[] states)317     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
318         if (VDBG) log("getDevicesMatchingStates()");
319         final IBluetoothA2dpSink service = mService;
320         if (service != null && isEnabled()) {
321             try {
322                 return service.getDevicesMatchingConnectionStates(states);
323             } catch (RemoteException e) {
324                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
325                 return new ArrayList<BluetoothDevice>();
326             }
327         }
328         if (service == null) Log.w(TAG, "Proxy not attached to service");
329         return new ArrayList<BluetoothDevice>();
330     }
331 
332     /**
333      * {@inheritDoc}
334      */
getConnectionState(BluetoothDevice device)335     public int getConnectionState(BluetoothDevice device) {
336         if (VDBG) log("getState(" + device + ")");
337         final IBluetoothA2dpSink service = mService;
338         if (service != null && isEnabled() && isValidDevice(device)) {
339             try {
340                 return service.getConnectionState(device);
341             } catch (RemoteException e) {
342                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
343                 return BluetoothProfile.STATE_DISCONNECTED;
344             }
345         }
346         if (service == null) Log.w(TAG, "Proxy not attached to service");
347         return BluetoothProfile.STATE_DISCONNECTED;
348     }
349 
350     /**
351      * Get the current audio configuration for the A2DP source device,
352      * or null if the device has no audio configuration
353      *
354      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
355      *
356      * @param device Remote bluetooth device.
357      * @return audio configuration for the device, or null
358      *
359      * {@see BluetoothAudioConfig}
360      */
getAudioConfig(BluetoothDevice device)361           public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
362         if (VDBG) log("getAudioConfig(" + device + ")");
363         final IBluetoothA2dpSink service = mService;
364         if (service != null && isEnabled() && isValidDevice(device)) {
365             try {
366                 return service.getAudioConfig(device);
367             } catch (RemoteException e) {
368                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
369                 return null;
370             }
371         }
372         if (service == null) Log.w(TAG, "Proxy not attached to service");
373         return null;
374     }
375 
376     /**
377      * Set priority of the profile
378      *
379      * <p> The device should already be paired.
380      *  Priority can be one of {@link #PRIORITY_ON} orgetBluetoothManager
381      * {@link #PRIORITY_OFF},
382      *
383      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
384      * permission.
385      *
386      * @param device Paired bluetooth device
387      * @param priority
388      * @return true if priority is set, false on error
389      * @hide
390      */
setPriority(BluetoothDevice device, int priority)391     public boolean setPriority(BluetoothDevice device, int priority) {
392         if (DBG) log("setPriority(" + device + ", " + priority + ")");
393         final IBluetoothA2dpSink service = mService;
394         if (service != null && isEnabled() && isValidDevice(device)) {
395             if (priority != BluetoothProfile.PRIORITY_OFF
396                     && priority != BluetoothProfile.PRIORITY_ON) {
397                 return false;
398             }
399             try {
400                 return service.setPriority(device, priority);
401             } catch (RemoteException e) {
402                    Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
403                    return false;
404             }
405         }
406         if (service == null) Log.w(TAG, "Proxy not attached to service");
407         return false;
408     }
409 
410     /**
411      * Get the priority of the profile.
412      *
413      * <p> The priority can be any of:
414      * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
415      * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
416      *
417      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
418      *
419      * @param device Bluetooth device
420      * @return priority of the device
421      * @hide
422      */
getPriority(BluetoothDevice device)423     public int getPriority(BluetoothDevice device) {
424         if (VDBG) log("getPriority(" + device + ")");
425         final IBluetoothA2dpSink service = mService;
426         if (service != null && isEnabled() && isValidDevice(device)) {
427             try {
428                 return service.getPriority(device);
429             } catch (RemoteException e) {
430                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
431                 return BluetoothProfile.PRIORITY_OFF;
432             }
433         }
434         if (service == null) Log.w(TAG, "Proxy not attached to service");
435         return BluetoothProfile.PRIORITY_OFF;
436     }
437 
438     /**
439      * Check if A2DP profile is streaming music.
440      *
441      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
442      *
443      * @param device BluetoothDevice device
444      */
isA2dpPlaying(BluetoothDevice device)445     public boolean isA2dpPlaying(BluetoothDevice device) {
446         final IBluetoothA2dpSink service = mService;
447         if (service != null && isEnabled() && isValidDevice(device)) {
448             try {
449                 return service.isA2dpPlaying(device);
450             } catch (RemoteException e) {
451                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
452                 return false;
453             }
454         }
455         if (service == null) Log.w(TAG, "Proxy not attached to service");
456         return false;
457     }
458 
459     /**
460      * Helper for converting a state to a string.
461      *
462      * For debug use only - strings are not internationalized.
463      * @hide
464      */
stateToString(int state)465     public static String stateToString(int state) {
466         switch (state) {
467         case STATE_DISCONNECTED:
468             return "disconnected";
469         case STATE_CONNECTING:
470             return "connecting";
471         case STATE_CONNECTED:
472             return "connected";
473         case STATE_DISCONNECTING:
474             return "disconnecting";
475         case STATE_PLAYING:
476             return "playing";
477         case STATE_NOT_PLAYING:
478           return "not playing";
479         default:
480             return "<unknown state " + state + ">";
481         }
482     }
483 
484     private final ServiceConnection mConnection = new ServiceConnection() {
485         public void onServiceConnected(ComponentName className, IBinder service) {
486             if (DBG) Log.d(TAG, "Proxy object connected");
487             mService = IBluetoothA2dpSink.Stub.asInterface(Binder.allowBlocking(service));
488             if (mServiceListener != null) {
489                 mServiceListener.onServiceConnected(BluetoothProfile.A2DP_SINK,
490                         BluetoothA2dpSink.this);
491             }
492         }
493         public void onServiceDisconnected(ComponentName className) {
494             if (DBG) Log.d(TAG, "Proxy object disconnected");
495             mService = null;
496             if (mServiceListener != null) {
497                 mServiceListener.onServiceDisconnected(BluetoothProfile.A2DP_SINK);
498             }
499         }
500     };
501 
isEnabled()502     private boolean isEnabled() {
503         return mAdapter.getState() == BluetoothAdapter.STATE_ON;
504     }
505 
isValidDevice(BluetoothDevice device)506     private static boolean isValidDevice(BluetoothDevice device) {
507         return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
508     }
509 
log(String msg)510     private static void log(String msg) {
511       Log.d(TAG, msg);
512     }
513 }
514