BluetoothA2dp.java revision c8fa4ff838a0c3d2c67db65540fa751e5abe27ed
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     * {@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     * {@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     * {@inheritDoc}
134     * @hide
135     */
136    public boolean connect(BluetoothDevice device) {
137        if (DBG) log("connect(" + device + ")");
138        if (mService != null && isEnabled() &&
139            isValidDevice(device)) {
140            try {
141                return mService.connect(device);
142            } catch (RemoteException e) {
143                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
144                return false;
145            }
146        }
147        if (mService == null) Log.w(TAG, "Proxy not attached to service");
148        return false;
149    }
150
151    /**
152     * {@inheritDoc}
153     * @hide
154     */
155    public boolean disconnect(BluetoothDevice device) {
156        if (DBG) log("disconnect(" + device + ")");
157        if (mService != null && isEnabled() &&
158            isValidDevice(device)) {
159            try {
160                return mService.disconnect(device);
161            } catch (RemoteException e) {
162                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
163                return false;
164            }
165        }
166        if (mService == null) Log.w(TAG, "Proxy not attached to service");
167        return false;
168    }
169
170    /**
171     * {@inheritDoc}
172     */
173    public List<BluetoothDevice> getConnectedDevices() {
174        if (DBG) log("getConnectedDevices()");
175        if (mService != null && isEnabled()) {
176            try {
177                return mService.getConnectedDevices();
178            } catch (RemoteException e) {
179                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
180                return new ArrayList<BluetoothDevice>();
181            }
182        }
183        if (mService == null) Log.w(TAG, "Proxy not attached to service");
184        return new ArrayList<BluetoothDevice>();
185    }
186
187    /**
188     * {@inheritDoc}
189     */
190    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
191        if (DBG) log("getDevicesMatchingStates()");
192        if (mService != null && isEnabled()) {
193            try {
194                return mService.getDevicesMatchingConnectionStates(states);
195            } catch (RemoteException e) {
196                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
197                return new ArrayList<BluetoothDevice>();
198            }
199        }
200        if (mService == null) Log.w(TAG, "Proxy not attached to service");
201        return new ArrayList<BluetoothDevice>();
202    }
203
204    /**
205     * {@inheritDoc}
206     */
207    public int getConnectionState(BluetoothDevice device) {
208        if (DBG) log("getState(" + device + ")");
209        if (mService != null && isEnabled()
210            && isValidDevice(device)) {
211            try {
212                return mService.getConnectionState(device);
213            } catch (RemoteException e) {
214                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
215                return BluetoothProfile.STATE_DISCONNECTED;
216            }
217        }
218        if (mService == null) Log.w(TAG, "Proxy not attached to service");
219        return BluetoothProfile.STATE_DISCONNECTED;
220    }
221
222    /**
223     * {@inheritDoc}
224     * @hide
225     */
226    public boolean setPriority(BluetoothDevice device, int priority) {
227        if (DBG) log("setPriority(" + device + ", " + priority + ")");
228        if (mService != null && isEnabled()
229            && isValidDevice(device)) {
230            if (priority != BluetoothProfile.PRIORITY_OFF &&
231                priority != BluetoothProfile.PRIORITY_ON) {
232              return false;
233            }
234            try {
235                return mService.setPriority(device, priority);
236            } catch (RemoteException e) {
237                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
238                return false;
239            }
240        }
241        if (mService == null) Log.w(TAG, "Proxy not attached to service");
242        return false;
243    }
244
245    /**
246     * {@inheritDoc}
247     * @hide
248     */
249    public int getPriority(BluetoothDevice device) {
250        if (DBG) log("getPriority(" + device + ")");
251        if (mService != null && isEnabled()
252            && isValidDevice(device)) {
253            try {
254                return mService.getPriority(device);
255            } catch (RemoteException e) {
256                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
257                return BluetoothProfile.PRIORITY_OFF;
258            }
259        }
260        if (mService == null) Log.w(TAG, "Proxy not attached to service");
261        return BluetoothProfile.PRIORITY_OFF;
262    }
263
264    /**
265     * Check if A2DP profile is streaming music.
266     *
267     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
268     *
269     * @param device BluetoothDevice device
270     */
271    public boolean isA2dpPlaying(BluetoothDevice device) {
272        if (mService != null && isEnabled()
273            && isValidDevice(device)) {
274            try {
275                return mService.isA2dpPlaying(device);
276            } catch (RemoteException e) {
277                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
278                return false;
279            }
280        }
281        if (mService == null) Log.w(TAG, "Proxy not attached to service");
282        return false;
283    }
284
285    /**
286     * Initiate suspend from an A2DP sink.
287     *
288     * <p> This API will return false in scenarios like the A2DP
289     * device is not in connected state etc. When this API returns,
290     * true, it is guaranteed that {@link #ACTION_CONNECTION_STATE_CHANGED}
291     * intent will be broadcasted with the state. Users can get the
292     * state of the A2DP device from this intent.
293     *
294     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
295     * permission.
296     *
297     * @param device Remote A2DP sink
298     * @return false on immediate error,
299     *               true otherwise
300     * @hide
301     */
302    public boolean suspendSink(BluetoothDevice device) {
303        if (mService != null && isEnabled()
304            && isValidDevice(device)) {
305            try {
306                return mService.suspendSink(device);
307            } catch (RemoteException e) {
308                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
309                return false;
310            }
311        }
312        if (mService == null) Log.w(TAG, "Proxy not attached to service");
313        return false;
314    }
315
316    /**
317     * Initiate resume from a suspended A2DP sink.
318     *
319     * <p> This API will return false in scenarios like the A2DP
320     * device is not in suspended state etc. When this API returns,
321     * true, it is guaranteed that {@link #ACTION_SINK_STATE_CHANGED}
322     * intent will be broadcasted with the state. Users can get the
323     * state of the A2DP device from this intent.
324     *
325     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
326     *
327     * @param device Remote A2DP sink
328     * @return false on immediate error,
329     *               true otherwise
330     * @hide
331     */
332    public boolean resumeSink(BluetoothDevice device) {
333        if (mService != null && isEnabled()
334            && isValidDevice(device)) {
335            try {
336                return mService.resumeSink(device);
337            } catch (RemoteException e) {
338                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
339                return false;
340            }
341        }
342        if (mService == null) Log.w(TAG, "Proxy not attached to service");
343        return false;
344    }
345
346    /**
347     * This function checks if the remote device is an AVCRP
348     * target and thus whether we should send volume keys
349     * changes or not.
350     * @hide
351     */
352    public boolean shouldSendVolumeKeys(BluetoothDevice device) {
353        if (isEnabled() && isValidDevice(device)) {
354            ParcelUuid[] uuids = device.getUuids();
355            if (uuids == null) return false;
356
357            for (ParcelUuid uuid: uuids) {
358                if (BluetoothUuid.isAvrcpTarget(uuid)) {
359                    return true;
360                }
361            }
362        }
363        return false;
364    }
365
366     /**
367     * Helper for converting a state to a string.
368     *
369     * For debug use only - strings are not internationalized.
370     * @hide
371     */
372    public static String stateToString(int state) {
373        switch (state) {
374        case STATE_DISCONNECTED:
375            return "disconnected";
376        case STATE_CONNECTING:
377            return "connecting";
378        case STATE_CONNECTED:
379            return "connected";
380        case STATE_DISCONNECTING:
381            return "disconnecting";
382        case STATE_PLAYING:
383            return "playing";
384        case STATE_NOT_PLAYING:
385          return "not playing";
386        default:
387            return "<unknown state " + state + ">";
388        }
389    }
390
391    private boolean isEnabled() {
392       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
393       return false;
394    }
395
396    private boolean isValidDevice(BluetoothDevice device) {
397       if (device == null) return false;
398
399       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
400       return false;
401    }
402
403    private static void log(String msg) {
404      Log.d(TAG, msg);
405    }
406}
407