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