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