• 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 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         if (mService != null && isEnabled() &&
243             isValidDevice(device)) {
244             try {
245                 return mService.connect(device);
246             } catch (RemoteException e) {
247                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
248                 return false;
249             }
250         }
251         if (mService == 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         if (mService != null && isEnabled() &&
284             isValidDevice(device)) {
285             try {
286                 return mService.disconnect(device);
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      * {@inheritDoc}
298      */
getConnectedDevices()299     public List<BluetoothDevice> getConnectedDevices() {
300         if (VDBG) log("getConnectedDevices()");
301         if (mService != null && isEnabled()) {
302             try {
303                 return mService.getConnectedDevices();
304             } catch (RemoteException e) {
305                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
306                 return new ArrayList<BluetoothDevice>();
307             }
308         }
309         if (mService == null) Log.w(TAG, "Proxy not attached to service");
310         return new ArrayList<BluetoothDevice>();
311     }
312 
313     /**
314      * {@inheritDoc}
315      */
getDevicesMatchingConnectionStates(int[] states)316     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
317         if (VDBG) log("getDevicesMatchingStates()");
318         if (mService != null && isEnabled()) {
319             try {
320                 return mService.getDevicesMatchingConnectionStates(states);
321             } catch (RemoteException e) {
322                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
323                 return new ArrayList<BluetoothDevice>();
324             }
325         }
326         if (mService == null) Log.w(TAG, "Proxy not attached to service");
327         return new ArrayList<BluetoothDevice>();
328     }
329 
330     /**
331      * {@inheritDoc}
332      */
getConnectionState(BluetoothDevice device)333     public int getConnectionState(BluetoothDevice device) {
334         if (VDBG) log("getState(" + device + ")");
335         if (mService != null && isEnabled()
336             && isValidDevice(device)) {
337             try {
338                 return mService.getConnectionState(device);
339             } catch (RemoteException e) {
340                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
341                 return BluetoothProfile.STATE_DISCONNECTED;
342             }
343         }
344         if (mService == null) Log.w(TAG, "Proxy not attached to service");
345         return BluetoothProfile.STATE_DISCONNECTED;
346     }
347 
348     /**
349      * Get the current audio configuration for the A2DP source device,
350      * or null if the device has no audio configuration
351      *
352      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
353      *
354      * @param device Remote bluetooth device.
355      * @return audio configuration for the device, or null
356      *
357      * {@see BluetoothAudioConfig}
358      */
getAudioConfig(BluetoothDevice device)359           public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
360         if (VDBG) log("getAudioConfig(" + device + ")");
361         if (mService != null && isEnabled()
362             && isValidDevice(device)) {
363             try {
364                 return mService.getAudioConfig(device);
365             } catch (RemoteException e) {
366                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
367                 return null;
368             }
369         }
370         if (mService == null) Log.w(TAG, "Proxy not attached to service");
371         return null;
372     }
373 
374     /**
375      * Set priority of the profile
376      *
377      * <p> The device should already be paired.
378      *  Priority can be one of {@link #PRIORITY_ON} orgetBluetoothManager
379      * {@link #PRIORITY_OFF},
380      *
381      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
382      * permission.
383      *
384      * @param device Paired bluetooth device
385      * @param priority
386      * @return true if priority is set, false on error
387      * @hide
388      */
setPriority(BluetoothDevice device, int priority)389     public boolean setPriority(BluetoothDevice device, int priority) {
390         if (DBG) log("setPriority(" + device + ", " + priority + ")");
391         if (mService != null && isEnabled()
392             && isValidDevice(device)) {
393             if (priority != BluetoothProfile.PRIORITY_OFF &&
394                 priority != BluetoothProfile.PRIORITY_ON){
395                 return false;
396             }
397             try {
398                 return mService.setPriority(device, priority);
399             } catch (RemoteException e) {
400                    Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
401                    return false;
402             }
403         }
404         if (mService == null) Log.w(TAG, "Proxy not attached to service");
405             return false;
406     }
407 
408     /**
409      * Get the priority of the profile.
410      *
411      * <p> The priority can be any of:
412      * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
413      * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
414      *
415      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
416      *
417      * @param device Bluetooth device
418      * @return priority of the device
419      * @hide
420      */
getPriority(BluetoothDevice device)421     public int getPriority(BluetoothDevice device) {
422         if (VDBG) log("getPriority(" + device + ")");
423         if (mService != null && isEnabled()
424             && isValidDevice(device)) {
425             try {
426                 return mService.getPriority(device);
427             } catch (RemoteException e) {
428                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
429                 return BluetoothProfile.PRIORITY_OFF;
430             }
431         }
432         if (mService == null) Log.w(TAG, "Proxy not attached to service");
433         return BluetoothProfile.PRIORITY_OFF;
434     }
435 
436     /**
437      * Check if A2DP profile is streaming music.
438      *
439      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
440      *
441      * @param device BluetoothDevice device
442      */
isA2dpPlaying(BluetoothDevice device)443     public boolean isA2dpPlaying(BluetoothDevice device) {
444         if (mService != null && isEnabled()
445             && isValidDevice(device)) {
446             try {
447                 return mService.isA2dpPlaying(device);
448             } catch (RemoteException e) {
449                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
450                 return false;
451             }
452         }
453         if (mService == null) Log.w(TAG, "Proxy not attached to service");
454         return false;
455     }
456 
457     /**
458      * Helper for converting a state to a string.
459      *
460      * For debug use only - strings are not internationalized.
461      * @hide
462      */
stateToString(int state)463     public static String stateToString(int state) {
464         switch (state) {
465         case STATE_DISCONNECTED:
466             return "disconnected";
467         case STATE_CONNECTING:
468             return "connecting";
469         case STATE_CONNECTED:
470             return "connected";
471         case STATE_DISCONNECTING:
472             return "disconnecting";
473         case STATE_PLAYING:
474             return "playing";
475         case STATE_NOT_PLAYING:
476           return "not playing";
477         default:
478             return "<unknown state " + state + ">";
479         }
480     }
481 
482     private final ServiceConnection mConnection = new ServiceConnection() {
483         public void onServiceConnected(ComponentName className, IBinder service) {
484             if (DBG) Log.d(TAG, "Proxy object connected");
485             mService = IBluetoothA2dpSink.Stub.asInterface(Binder.allowBlocking(service));
486 
487             if (mServiceListener != null) {
488                 mServiceListener.onServiceConnected(BluetoothProfile.A2DP_SINK,
489                         BluetoothA2dpSink.this);
490             }
491         }
492         public void onServiceDisconnected(ComponentName className) {
493             if (DBG) Log.d(TAG, "Proxy object disconnected");
494             mService = null;
495             if (mServiceListener != null) {
496                 mServiceListener.onServiceDisconnected(BluetoothProfile.A2DP_SINK);
497             }
498         }
499     };
500 
isEnabled()501     private boolean isEnabled() {
502        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
503        return false;
504     }
505 
isValidDevice(BluetoothDevice device)506     private boolean isValidDevice(BluetoothDevice device) {
507        if (device == null) return false;
508 
509        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
510        return false;
511     }
512 
log(String msg)513     private static void log(String msg) {
514       Log.d(TAG, msg);
515     }
516 }
517