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