blob: 920ef89d0af3c154904296129d16fc8cb83c01ab [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.content.Context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.os.IBinder;
Jaikumar Ganesh41d5c802010-10-13 19:11:28 -070023import android.os.ParcelUuid;
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070024import android.os.RemoteException;
25import android.os.ServiceManager;
26import android.server.BluetoothA2dpService;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.util.Log;
28
Nick Pellybd022f42009-08-14 18:33:38 -070029import java.util.Collections;
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070030import java.util.Arrays;
31import java.util.HashMap;
Nick Pellybd022f42009-08-14 18:33:38 -070032import java.util.HashSet;
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070033import java.util.Set;
34
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035
36/**
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070037 * This class provides the public APIs to control the Bluetooth A2DP
38 * profile.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039 *
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070040 *<p>BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP
41 * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
42 * the BluetoothA2dp proxy object.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043 *
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070044 * <p> Android only supports one connected Bluetooth A2dp device at a time.
45 * Each method is protected with its appropriate permission.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046 */
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070047public final class BluetoothA2dp implements BluetoothProfile {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048 private static final String TAG = "BluetoothA2dp";
The Android Open Source Projectf5b4b982009-03-05 20:00:43 -080049 private static final boolean DBG = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070051 /**
52 * Intent used to broadcast the change in connection state of the A2DP
53 * profile.
54 *
55 * <p>This intent will have 3 extras:
56 * {@link #EXTRA_STATE} - The current state of the profile.
57 * {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile
58 * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
59 *
60 * {@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} to receive.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065 */
66 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070067 public static final String ACTION_CONNECTION_STATE_CHANGED =
68 "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070070 /**
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 * {@link #EXTRA_STATE} - The current state of the profile.
76 * {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile
77 * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
78 *
79 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
80 * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING},
81 *
82 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
83 */
84 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
85 public static final String ACTION_PLAYING_STATE_CHANGED =
86 "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080087
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070088 /**
89 * A2DP sink device is streaming music. This state can be one of
90 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
91 * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
92 */
93 public static final int STATE_PLAYING = 10;
Nick Pellybd022f42009-08-14 18:33:38 -070094
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -070095 /**
96 * A2DP sink device is NOT streaming music. This state can be one of
97 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
98 * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
99 */
100 public static final int STATE_NOT_PLAYING = 11;
101
102 private ServiceListener mServiceListener;
103 private IBluetoothA2dp mService;
104 private BluetoothAdapter mAdapter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800105
106 /**
107 * Create a BluetoothA2dp proxy object for interacting with the local
108 * Bluetooth A2DP service.
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700109 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110 */
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700111 /*package*/ BluetoothA2dp(Context mContext, ServiceListener l) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112 IBinder b = ServiceManager.getService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE);
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700113 mServiceListener = l;
114 mAdapter = BluetoothAdapter.getDefaultAdapter();
Michael Chan081f58a2009-09-30 17:59:40 -0700115 if (b != null) {
116 mService = IBluetoothA2dp.Stub.asInterface(b);
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700117 if (mServiceListener != null) {
118 mServiceListener.onServiceConnected(BluetoothProfile.A2DP, this);
119 }
Michael Chan081f58a2009-09-30 17:59:40 -0700120 } else {
121 Log.w(TAG, "Bluetooth A2DP service not available!");
122
123 // Instead of throwing an exception which prevents people from going
124 // into Wireless settings in the emulator. Let it crash later when it is actually used.
125 mService = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800126 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800127 }
128
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700129 /**
130 * {@inheritDoc}
131 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800132 */
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700133 public boolean connect(BluetoothDevice device) {
134 if (DBG) log("connect(" + device + ")");
135 if (mService != null && isEnabled() &&
136 isValidDevice(device)) {
137 try {
138 return mService.connect(device);
139 } catch (RemoteException e) {
140 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
141 return false;
142 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800143 }
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700144 if (mService == null) Log.w(TAG, "Proxy not attached to service");
145 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800146 }
147
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700148 /**
149 * {@inheritDoc}
150 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800151 */
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700152 public boolean disconnect(BluetoothDevice device) {
153 if (DBG) log("disconnect(" + device + ")");
154 if (mService != null && isEnabled() &&
155 isValidDevice(device)) {
156 try {
157 return mService.disconnect(device);
158 } catch (RemoteException e) {
159 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
160 return false;
161 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800162 }
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700163 if (mService == null) Log.w(TAG, "Proxy not attached to service");
164 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165 }
166
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700167 /**
168 * {@inheritDoc}
169 */
170 public Set<BluetoothDevice> getConnectedDevices() {
171 if (DBG) log("getConnectedDevices()");
172 if (mService != null && isEnabled()) {
173 try {
174 return toDeviceSet(mService.getConnectedDevices());
175 } catch (RemoteException e) {
176 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
177 return toDeviceSet(new BluetoothDevice[0]);
178 }
179 }
180 if (mService == null) Log.w(TAG, "Proxy not attached to service");
181 return toDeviceSet(new BluetoothDevice[0]);
182 }
183
184 /**
185 * {@inheritDoc}
186 */
187 public Set<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
188 if (DBG) log("getDevicesMatchingStates()");
189 if (mService != null && isEnabled()) {
190 try {
191 return toDeviceSet(mService.getDevicesMatchingConnectionStates(states));
192 } catch (RemoteException e) {
193 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
194 return toDeviceSet(new BluetoothDevice[0]);
195 }
196 }
197 if (mService == null) Log.w(TAG, "Proxy not attached to service");
198 return toDeviceSet(new BluetoothDevice[0]);
199 }
200
201 /**
202 * {@inheritDoc}
203 */
204 public int getConnectionState(BluetoothDevice device) {
205 if (DBG) log("getState(" + device + ")");
206 if (mService != null && isEnabled()
207 && isValidDevice(device)) {
208 try {
209 return mService.getConnectionState(device);
210 } catch (RemoteException e) {
211 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
212 return BluetoothProfile.STATE_DISCONNECTED;
213 }
214 }
215 if (mService == null) Log.w(TAG, "Proxy not attached to service");
216 return BluetoothProfile.STATE_DISCONNECTED;
217 }
218
219 /**
220 * {@inheritDoc}
221 * @hide
222 */
223 public boolean setPriority(BluetoothDevice device, int priority) {
224 if (DBG) log("setPriority(" + device + ", " + priority + ")");
225 if (mService != null && isEnabled()
226 && isValidDevice(device)) {
227 if (priority != BluetoothProfile.PRIORITY_OFF &&
228 priority != BluetoothProfile.PRIORITY_ON) {
229 return false;
230 }
231 try {
232 return mService.setPriority(device, priority);
233 } catch (RemoteException e) {
234 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
235 return false;
236 }
237 }
238 if (mService == null) Log.w(TAG, "Proxy not attached to service");
239 return false;
240 }
241
242 /**
243 * {@inheritDoc}
244 * @hide
245 */
246 public int getPriority(BluetoothDevice device) {
247 if (DBG) log("getPriority(" + device + ")");
248 if (mService != null && isEnabled()
249 && isValidDevice(device)) {
250 try {
251 return mService.getPriority(device);
252 } catch (RemoteException e) {
253 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
254 return BluetoothProfile.PRIORITY_OFF;
255 }
256 }
257 if (mService == null) Log.w(TAG, "Proxy not attached to service");
258 return BluetoothProfile.PRIORITY_OFF;
259 }
260
261 /**
262 * Check if A2DP profile is streaming music.
263 *
264 * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
265 *
266 * @param device BluetoothDevice device
267 */
268 public boolean isA2dpPlaying(BluetoothDevice device) {
269 if (mService != null && isEnabled()
270 && isValidDevice(device)) {
271 try {
272 return mService.isA2dpPlaying(device);
273 } catch (RemoteException e) {
274 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
275 return false;
276 }
277 }
278 if (mService == null) Log.w(TAG, "Proxy not attached to service");
279 return false;
280 }
281
282 /**
283 * Initiate suspend from an A2DP sink.
284 *
285 * <p> This API will return false in scenarios like the A2DP
286 * device is not in connected state etc. When this API returns,
287 * true, it is guaranteed that {@link #ACTION_SINK_STATE_CHANGED}
288 * intent will be broadcasted with the state. Users can get the
289 * state of the A2DP device from this intent.
290 *
291 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
292 *
293 * @param device Remote A2DP sink
294 * @return false on immediate error,
295 * true otherwise
296 * @hide
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800297 */
Nick Pellye766eae2009-09-30 12:06:28 -0700298 public boolean suspendSink(BluetoothDevice device) {
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700299 if (mService != null && isEnabled()
300 && isValidDevice(device)) {
301 try {
302 return mService.suspendSink(device);
303 } catch (RemoteException e) {
304 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
305 return false;
306 }
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800307 }
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700308 if (mService == null) Log.w(TAG, "Proxy not attached to service");
309 return false;
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800310 }
311
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700312 /**
313 * Initiate resume from a suspended A2DP sink.
314 *
315 * <p> This API will return false in scenarios like the A2DP
316 * device is not in suspended state etc. When this API returns,
317 * true, it is guaranteed that {@link #ACTION_SINK_STATE_CHANGED}
318 * intent will be broadcasted with the state. Users can get the
319 * state of the A2DP device from this intent.
320 *
321 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
322 *
323 * @param device Remote A2DP sink
324 * @return false on immediate error,
325 * true otherwise
326 * @hide
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800327 */
Nick Pellye766eae2009-09-30 12:06:28 -0700328 public boolean resumeSink(BluetoothDevice device) {
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700329 if (mService != null && isEnabled()
330 && isValidDevice(device)) {
331 try {
332 return mService.resumeSink(device);
333 } catch (RemoteException e) {
334 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
335 return false;
336 }
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800337 }
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700338 if (mService == null) Log.w(TAG, "Proxy not attached to service");
339 return false;
Zhu Lanf9bbe1e2009-06-24 10:51:57 +0800340 }
341
Jaikumar Ganesh41d5c802010-10-13 19:11:28 -0700342 /**
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
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700362 /**
363 * Helper for converting a state to a string.
364 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800365 * 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";
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700380 case STATE_NOT_PLAYING:
381 return "not playing";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800382 default:
383 return "<unknown state " + state + ">";
384 }
385 }
The Android Open Source Projectf5b4b982009-03-05 20:00:43 -0800386
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700387 private boolean isEnabled() {
388 if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
389 return false;
390 }
391
392 private boolean isValidDevice(BluetoothDevice device) {
393 if (device == null) return false;
394
395 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
396 return false;
397 }
398
399 private Set<BluetoothDevice> toDeviceSet(BluetoothDevice[] devices) {
400 return Collections.unmodifiableSet(
401 new HashSet<BluetoothDevice>(Arrays.asList(devices)));
402 }
403
The Android Open Source Projectf5b4b982009-03-05 20:00:43 -0800404 private static void log(String msg) {
Jaikumar Ganesh62c37ef2010-08-24 17:36:13 -0700405 Log.d(TAG, msg);
The Android Open Source Projectf5b4b982009-03-05 20:00:43 -0800406 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800407}