blob: 5374d6d55ee39fd88f96697d601b7f2c3eb2a5e6 [file] [log] [blame]
Alan Viverette3da604b2020-06-10 18:34:39 +00001/*
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.Manifest;
20import android.annotation.IntDef;
21import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.annotation.RequiresPermission;
24import android.annotation.SdkConstant;
25import android.annotation.SdkConstant.SdkConstantType;
26import android.annotation.SystemApi;
27import android.compat.annotation.UnsupportedAppUsage;
28import android.content.Context;
29import android.os.Binder;
30import android.os.Build;
31import android.os.IBinder;
32import android.os.ParcelUuid;
33import android.os.RemoteException;
34import android.util.Log;
35
36import java.lang.annotation.Retention;
37import java.lang.annotation.RetentionPolicy;
38import java.util.ArrayList;
39import java.util.List;
40
41
42/**
43 * This class provides the public APIs to control the Bluetooth A2DP
44 * profile.
45 *
46 * <p>BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP
47 * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
48 * the BluetoothA2dp proxy object.
49 *
50 * <p> Android only supports one connected Bluetooth A2dp device at a time.
51 * Each method is protected with its appropriate permission.
52 */
53public final class BluetoothA2dp implements BluetoothProfile {
54 private static final String TAG = "BluetoothA2dp";
55 private static final boolean DBG = true;
56 private static final boolean VDBG = false;
57
58 /**
59 * Intent used to broadcast the change in connection state of the A2DP
60 * profile.
61 *
62 * <p>This intent will have 3 extras:
63 * <ul>
64 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
65 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
66 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
67 * </ul>
68 *
69 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
70 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
71 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
72 *
73 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
74 * receive.
75 */
76 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
77 public static final String ACTION_CONNECTION_STATE_CHANGED =
78 "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED";
79
80 /**
81 * Intent used to broadcast the change in the Playing state of the A2DP
82 * profile.
83 *
84 * <p>This intent will have 3 extras:
85 * <ul>
86 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
87 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
88 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
89 * </ul>
90 *
91 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
92 * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING},
93 *
94 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
95 * receive.
96 */
97 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
98 public static final String ACTION_PLAYING_STATE_CHANGED =
99 "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED";
100
101 /** @hide */
102 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
103 public static final String ACTION_AVRCP_CONNECTION_STATE_CHANGED =
104 "android.bluetooth.a2dp.profile.action.AVRCP_CONNECTION_STATE_CHANGED";
105
106 /**
107 * Intent used to broadcast the selection of a connected device as active.
108 *
109 * <p>This intent will have one extra:
110 * <ul>
111 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can
112 * be null if no device is active. </li>
113 * </ul>
114 *
115 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
116 * receive.
117 *
118 * @hide
119 */
120 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
121 @UnsupportedAppUsage
122 public static final String ACTION_ACTIVE_DEVICE_CHANGED =
123 "android.bluetooth.a2dp.profile.action.ACTIVE_DEVICE_CHANGED";
124
125 /**
126 * Intent used to broadcast the change in the Audio Codec state of the
127 * A2DP Source profile.
128 *
129 * <p>This intent will have 2 extras:
130 * <ul>
131 * <li> {@link BluetoothCodecStatus#EXTRA_CODEC_STATUS} - The codec status. </li>
132 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device if the device is currently
133 * connected, otherwise it is not included.</li>
134 * </ul>
135 *
136 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
137 * receive.
138 *
139 * @hide
140 */
141 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
142 @UnsupportedAppUsage
143 public static final String ACTION_CODEC_CONFIG_CHANGED =
144 "android.bluetooth.a2dp.profile.action.CODEC_CONFIG_CHANGED";
145
146 /**
147 * A2DP sink device is streaming music. This state can be one of
148 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
149 * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
150 */
151 public static final int STATE_PLAYING = 10;
152
153 /**
154 * A2DP sink device is NOT streaming music. This state can be one of
155 * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
156 * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
157 */
158 public static final int STATE_NOT_PLAYING = 11;
159
160 /** @hide */
161 @IntDef(prefix = "OPTIONAL_CODECS_", value = {
162 OPTIONAL_CODECS_SUPPORT_UNKNOWN,
163 OPTIONAL_CODECS_NOT_SUPPORTED,
164 OPTIONAL_CODECS_SUPPORTED
165 })
166 @Retention(RetentionPolicy.SOURCE)
167 public @interface OptionalCodecsSupportStatus {}
168
169 /**
170 * We don't have a stored preference for whether or not the given A2DP sink device supports
171 * optional codecs.
172 *
173 * @hide
174 */
175 @SystemApi
176 public static final int OPTIONAL_CODECS_SUPPORT_UNKNOWN = -1;
177
178 /**
179 * The given A2DP sink device does not support optional codecs.
180 *
181 * @hide
182 */
183 @SystemApi
184 public static final int OPTIONAL_CODECS_NOT_SUPPORTED = 0;
185
186 /**
187 * The given A2DP sink device does support optional codecs.
188 *
189 * @hide
190 */
191 @SystemApi
192 public static final int OPTIONAL_CODECS_SUPPORTED = 1;
193
194 /** @hide */
195 @IntDef(prefix = "OPTIONAL_CODECS_PREF_", value = {
196 OPTIONAL_CODECS_PREF_UNKNOWN,
197 OPTIONAL_CODECS_PREF_DISABLED,
198 OPTIONAL_CODECS_PREF_ENABLED
199 })
200 @Retention(RetentionPolicy.SOURCE)
201 public @interface OptionalCodecsPreferenceStatus {}
202
203 /**
204 * We don't have a stored preference for whether optional codecs should be enabled or
205 * disabled for the given A2DP device.
206 *
207 * @hide
208 */
209 @SystemApi
210 public static final int OPTIONAL_CODECS_PREF_UNKNOWN = -1;
211
212 /**
213 * Optional codecs should be disabled for the given A2DP device.
214 *
215 * @hide
216 */
217 @SystemApi
218 public static final int OPTIONAL_CODECS_PREF_DISABLED = 0;
219
220 /**
221 * Optional codecs should be enabled for the given A2DP device.
222 *
223 * @hide
224 */
225 @SystemApi
226 public static final int OPTIONAL_CODECS_PREF_ENABLED = 1;
227
228 private BluetoothAdapter mAdapter;
229 private final BluetoothProfileConnector<IBluetoothA2dp> mProfileConnector =
230 new BluetoothProfileConnector(this, BluetoothProfile.A2DP, "BluetoothA2dp",
231 IBluetoothA2dp.class.getName()) {
232 @Override
233 public IBluetoothA2dp getServiceInterface(IBinder service) {
234 return IBluetoothA2dp.Stub.asInterface(Binder.allowBlocking(service));
235 }
236 };
237
238 /**
239 * Create a BluetoothA2dp proxy object for interacting with the local
240 * Bluetooth A2DP service.
241 */
242 /*package*/ BluetoothA2dp(Context context, ServiceListener listener) {
243 mAdapter = BluetoothAdapter.getDefaultAdapter();
244 mProfileConnector.connect(context, listener);
245 }
246
247 @UnsupportedAppUsage
248 /*package*/ void close() {
249 mProfileConnector.disconnect();
250 }
251
252 private IBluetoothA2dp getService() {
253 return mProfileConnector.getService();
254 }
255
256 @Override
257 public void finalize() {
258 // The empty finalize needs to be kept or the
259 // cts signature tests would fail.
260 }
261
262 /**
263 * Initiate connection to a profile of the remote Bluetooth device.
264 *
265 * <p> This API returns false in scenarios like the profile on the
266 * device is already connected or Bluetooth is not turned on.
267 * When this API returns true, it is guaranteed that
268 * connection state intent for the profile will be broadcasted with
269 * the state. Users can get the connection state of the profile
270 * from this intent.
271 *
272 *
273 * @param device Remote Bluetooth Device
274 * @return false on immediate error, true otherwise
275 * @hide
276 */
277 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
278 @UnsupportedAppUsage
279 public boolean connect(BluetoothDevice device) {
280 if (DBG) log("connect(" + device + ")");
281 try {
282 final IBluetoothA2dp service = getService();
283 if (service != null && isEnabled() && isValidDevice(device)) {
284 return service.connect(device);
285 }
286 if (service == null) Log.w(TAG, "Proxy not attached to service");
287 return false;
288 } catch (RemoteException e) {
289 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
290 return false;
291 }
292 }
293
294 /**
295 * Initiate disconnection from a profile
296 *
297 * <p> This API will return false in scenarios like the profile on the
298 * Bluetooth device is not in connected state etc. When this API returns,
299 * true, it is guaranteed that the connection state change
300 * intent will be broadcasted with the state. Users can get the
301 * disconnection state of the profile from this intent.
302 *
303 * <p> If the disconnection is initiated by a remote device, the state
304 * will transition from {@link #STATE_CONNECTED} to
305 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
306 * host (local) device the state will transition from
307 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
308 * state {@link #STATE_DISCONNECTED}. The transition to
309 * {@link #STATE_DISCONNECTING} can be used to distinguish between the
310 * two scenarios.
311 *
312 *
313 * @param device Remote Bluetooth Device
314 * @return false on immediate error, true otherwise
315 * @hide
316 */
317 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
318 @UnsupportedAppUsage
319 public boolean disconnect(BluetoothDevice device) {
320 if (DBG) log("disconnect(" + device + ")");
321 try {
322 final IBluetoothA2dp service = getService();
323 if (service != null && isEnabled() && isValidDevice(device)) {
324 return service.disconnect(device);
325 }
326 if (service == null) Log.w(TAG, "Proxy not attached to service");
327 return false;
328 } catch (RemoteException e) {
329 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
330 return false;
331 }
332 }
333
334 /**
335 * {@inheritDoc}
336 */
337 @Override
338 public List<BluetoothDevice> getConnectedDevices() {
339 if (VDBG) log("getConnectedDevices()");
340 try {
341 final IBluetoothA2dp service = getService();
342 if (service != null && isEnabled()) {
343 return service.getConnectedDevices();
344 }
345 if (service == null) Log.w(TAG, "Proxy not attached to service");
346 return new ArrayList<BluetoothDevice>();
347 } catch (RemoteException e) {
348 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
349 return new ArrayList<BluetoothDevice>();
350 }
351 }
352
353 /**
354 * {@inheritDoc}
355 */
356 @Override
357 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
358 if (VDBG) log("getDevicesMatchingStates()");
359 try {
360 final IBluetoothA2dp service = getService();
361 if (service != null && isEnabled()) {
362 return service.getDevicesMatchingConnectionStates(states);
363 }
364 if (service == null) Log.w(TAG, "Proxy not attached to service");
365 return new ArrayList<BluetoothDevice>();
366 } catch (RemoteException e) {
367 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
368 return new ArrayList<BluetoothDevice>();
369 }
370 }
371
372 /**
373 * {@inheritDoc}
374 */
375 @Override
376 public @BtProfileState int getConnectionState(BluetoothDevice device) {
377 if (VDBG) log("getState(" + device + ")");
378 try {
379 final IBluetoothA2dp service = getService();
380 if (service != null && isEnabled()
381 && isValidDevice(device)) {
382 return service.getConnectionState(device);
383 }
384 if (service == null) Log.w(TAG, "Proxy not attached to service");
385 return BluetoothProfile.STATE_DISCONNECTED;
386 } catch (RemoteException e) {
387 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
388 return BluetoothProfile.STATE_DISCONNECTED;
389 }
390 }
391
392 /**
393 * Select a connected device as active.
394 *
395 * The active device selection is per profile. An active device's
396 * purpose is profile-specific. For example, A2DP audio streaming
397 * is to the active A2DP Sink device. If a remote device is not
398 * connected, it cannot be selected as active.
399 *
400 * <p> This API returns false in scenarios like the profile on the
401 * device is not connected or Bluetooth is not turned on.
402 * When this API returns true, it is guaranteed that the
403 * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
404 * with the active device.
405 *
406 * @param device the remote Bluetooth device. Could be null to clear
407 * the active device and stop streaming audio to a Bluetooth device.
408 * @return false on immediate error, true otherwise
409 * @hide
410 */
411 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
412 @UnsupportedAppUsage
413 public boolean setActiveDevice(@Nullable BluetoothDevice device) {
414 if (DBG) log("setActiveDevice(" + device + ")");
415 try {
416 final IBluetoothA2dp service = getService();
417 if (service != null && isEnabled()
418 && ((device == null) || isValidDevice(device))) {
419 return service.setActiveDevice(device);
420 }
421 if (service == null) Log.w(TAG, "Proxy not attached to service");
422 return false;
423 } catch (RemoteException e) {
424 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
425 return false;
426 }
427 }
428
429 /**
430 * Get the connected device that is active.
431 *
432 * @return the connected device that is active or null if no device
433 * is active
434 * @hide
435 */
436 @UnsupportedAppUsage
437 @Nullable
438 @RequiresPermission(Manifest.permission.BLUETOOTH)
439 public BluetoothDevice getActiveDevice() {
440 if (VDBG) log("getActiveDevice()");
441 try {
442 final IBluetoothA2dp service = getService();
443 if (service != null && isEnabled()) {
444 return service.getActiveDevice();
445 }
446 if (service == null) Log.w(TAG, "Proxy not attached to service");
447 return null;
448 } catch (RemoteException e) {
449 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
450 return null;
451 }
452 }
453
454 /**
455 * Set priority of the profile
456 *
457 * <p> The device should already be paired.
458 * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}
459 *
460 * @param device Paired bluetooth device
461 * @param priority
462 * @return true if priority is set, false on error
463 * @hide
464 */
465 @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
466 public boolean setPriority(BluetoothDevice device, int priority) {
467 if (DBG) log("setPriority(" + device + ", " + priority + ")");
468 return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
469 }
470
471 /**
472 * Set connection policy of the profile
473 *
474 * <p> The device should already be paired.
475 * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
476 * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
477 *
478 * @param device Paired bluetooth device
479 * @param connectionPolicy is the connection policy to set to for this profile
480 * @return true if connectionPolicy is set, false on error
481 * @hide
482 */
483 @SystemApi
484 @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
485 public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
486 @ConnectionPolicy int connectionPolicy) {
487 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
488 try {
489 final IBluetoothA2dp service = getService();
490 if (service != null && isEnabled()
491 && isValidDevice(device)) {
492 if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
493 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
494 return false;
495 }
496 return service.setConnectionPolicy(device, connectionPolicy);
497 }
498 if (service == null) Log.w(TAG, "Proxy not attached to service");
499 return false;
500 } catch (RemoteException e) {
501 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
502 return false;
503 }
504 }
505
506 /**
507 * Get the priority of the profile.
508 *
509 * <p> The priority can be any of:
510 * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
511 *
512 * @param device Bluetooth device
513 * @return priority of the device
514 * @hide
515 */
516 @RequiresPermission(Manifest.permission.BLUETOOTH)
517 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
518 public int getPriority(BluetoothDevice device) {
519 if (VDBG) log("getPriority(" + device + ")");
520 try {
521 final IBluetoothA2dp service = getService();
522 if (service != null && isEnabled()
523 && isValidDevice(device)) {
524 return BluetoothAdapter.connectionPolicyToPriority(service.getPriority(device));
525 }
526 if (service == null) Log.w(TAG, "Proxy not attached to service");
527 return BluetoothProfile.PRIORITY_OFF;
528 } catch (RemoteException e) {
529 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
530 return BluetoothProfile.PRIORITY_OFF;
531 }
532 }
533
534 /**
535 * Get the connection policy of the profile.
536 *
537 * <p> The connection policy can be any of:
538 * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
539 * {@link #CONNECTION_POLICY_UNKNOWN}
540 *
541 * @param device Bluetooth device
542 * @return connection policy of the device
543 * @hide
544 */
545 @SystemApi
546 @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
547 public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
548 if (VDBG) log("getConnectionPolicy(" + device + ")");
549 try {
550 final IBluetoothA2dp service = getService();
551 if (service != null && isEnabled()
552 && isValidDevice(device)) {
553 return service.getConnectionPolicy(device);
554 }
555 if (service == null) Log.w(TAG, "Proxy not attached to service");
556 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
557 } catch (RemoteException e) {
558 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
559 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
560 }
561 }
562
563 /**
564 * Checks if Avrcp device supports the absolute volume feature.
565 *
566 * @return true if device supports absolute volume
567 * @hide
568 */
569 public boolean isAvrcpAbsoluteVolumeSupported() {
570 if (DBG) Log.d(TAG, "isAvrcpAbsoluteVolumeSupported");
571 try {
572 final IBluetoothA2dp service = getService();
573 if (service != null && isEnabled()) {
574 return service.isAvrcpAbsoluteVolumeSupported();
575 }
576 if (service == null) Log.w(TAG, "Proxy not attached to service");
577 return false;
578 } catch (RemoteException e) {
579 Log.e(TAG, "Error talking to BT service in isAvrcpAbsoluteVolumeSupported()", e);
580 return false;
581 }
582 }
583
584 /**
585 * Tells remote device to set an absolute volume. Only if absolute volume is supported
586 *
587 * @param volume Absolute volume to be set on AVRCP side
588 * @hide
589 */
590 public void setAvrcpAbsoluteVolume(int volume) {
591 if (DBG) Log.d(TAG, "setAvrcpAbsoluteVolume");
592 try {
593 final IBluetoothA2dp service = getService();
594 if (service != null && isEnabled()) {
595 service.setAvrcpAbsoluteVolume(volume);
596 }
597 if (service == null) Log.w(TAG, "Proxy not attached to service");
598 } catch (RemoteException e) {
599 Log.e(TAG, "Error talking to BT service in setAvrcpAbsoluteVolume()", e);
600 }
601 }
602
603 /**
604 * Check if A2DP profile is streaming music.
605 *
606 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
607 *
608 * @param device BluetoothDevice device
609 */
610 public boolean isA2dpPlaying(BluetoothDevice device) {
611 try {
612 final IBluetoothA2dp service = getService();
613 if (service != null && isEnabled()
614 && isValidDevice(device)) {
615 return service.isA2dpPlaying(device);
616 }
617 if (service == null) Log.w(TAG, "Proxy not attached to service");
618 return false;
619 } catch (RemoteException e) {
620 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
621 return false;
622 }
623 }
624
625 /**
626 * This function checks if the remote device is an AVCRP
627 * target and thus whether we should send volume keys
628 * changes or not.
629 *
630 * @hide
631 */
632 public boolean shouldSendVolumeKeys(BluetoothDevice device) {
633 if (isEnabled() && isValidDevice(device)) {
634 ParcelUuid[] uuids = device.getUuids();
635 if (uuids == null) return false;
636
637 for (ParcelUuid uuid : uuids) {
638 if (uuid.equals(BluetoothUuid.AVRCP_TARGET)) {
639 return true;
640 }
641 }
642 }
643 return false;
644 }
645
646 /**
647 * Gets the current codec status (configuration and capability).
648 *
649 * @param device the remote Bluetooth device. If null, use the current
650 * active A2DP Bluetooth device.
651 * @return the current codec status
652 * @hide
653 */
654 @UnsupportedAppUsage
655 @Nullable
656 @RequiresPermission(Manifest.permission.BLUETOOTH)
657 public BluetoothCodecStatus getCodecStatus(@NonNull BluetoothDevice device) {
658 if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")");
659 verifyDeviceNotNull(device, "getCodecStatus");
660 try {
661 final IBluetoothA2dp service = getService();
662 if (service != null && isEnabled()) {
663 return service.getCodecStatus(device);
664 }
665 if (service == null) {
666 Log.w(TAG, "Proxy not attached to service");
667 }
668 return null;
669 } catch (RemoteException e) {
670 Log.e(TAG, "Error talking to BT service in getCodecStatus()", e);
671 return null;
672 }
673 }
674
675 /**
676 * Sets the codec configuration preference.
677 *
678 * @param device the remote Bluetooth device. If null, use the current
679 * active A2DP Bluetooth device.
680 * @param codecConfig the codec configuration preference
681 * @hide
682 */
683 @UnsupportedAppUsage
684 @RequiresPermission(Manifest.permission.BLUETOOTH)
685 public void setCodecConfigPreference(@NonNull BluetoothDevice device,
686 @NonNull BluetoothCodecConfig codecConfig) {
687 if (DBG) Log.d(TAG, "setCodecConfigPreference(" + device + ")");
688 verifyDeviceNotNull(device, "setCodecConfigPreference");
689 if (codecConfig == null) {
690 Log.e(TAG, "setCodecConfigPreference: Codec config can't be null");
691 throw new IllegalArgumentException("codecConfig cannot be null");
692 }
693 try {
694 final IBluetoothA2dp service = getService();
695 if (service != null && isEnabled()) {
696 service.setCodecConfigPreference(device, codecConfig);
697 }
698 if (service == null) Log.w(TAG, "Proxy not attached to service");
699 return;
700 } catch (RemoteException e) {
701 Log.e(TAG, "Error talking to BT service in setCodecConfigPreference()", e);
702 return;
703 }
704 }
705
706 /**
707 * Enables the optional codecs.
708 *
709 * @param device the remote Bluetooth device. If null, use the currect
710 * active A2DP Bluetooth device.
711 * @hide
712 */
713 @UnsupportedAppUsage
714 @RequiresPermission(Manifest.permission.BLUETOOTH)
715 public void enableOptionalCodecs(@NonNull BluetoothDevice device) {
716 if (DBG) Log.d(TAG, "enableOptionalCodecs(" + device + ")");
717 verifyDeviceNotNull(device, "enableOptionalCodecs");
718 enableDisableOptionalCodecs(device, true);
719 }
720
721 /**
722 * Disables the optional codecs.
723 *
724 * @param device the remote Bluetooth device. If null, use the currect
725 * active A2DP Bluetooth device.
726 * @hide
727 */
728 @UnsupportedAppUsage
729 @RequiresPermission(Manifest.permission.BLUETOOTH)
730 public void disableOptionalCodecs(@NonNull BluetoothDevice device) {
731 if (DBG) Log.d(TAG, "disableOptionalCodecs(" + device + ")");
732 verifyDeviceNotNull(device, "disableOptionalCodecs");
733 enableDisableOptionalCodecs(device, false);
734 }
735
736 /**
737 * Enables or disables the optional codecs.
738 *
739 * @param device the remote Bluetooth device. If null, use the currect
740 * active A2DP Bluetooth device.
741 * @param enable if true, enable the optional codecs, other disable them
742 */
743 private void enableDisableOptionalCodecs(BluetoothDevice device, boolean enable) {
744 try {
745 final IBluetoothA2dp service = getService();
746 if (service != null && isEnabled()) {
747 if (enable) {
748 service.enableOptionalCodecs(device);
749 } else {
750 service.disableOptionalCodecs(device);
751 }
752 }
753 if (service == null) Log.w(TAG, "Proxy not attached to service");
754 return;
755 } catch (RemoteException e) {
756 Log.e(TAG, "Error talking to BT service in enableDisableOptionalCodecs()", e);
757 return;
758 }
759 }
760
761 /**
762 * Returns whether this device supports optional codecs.
763 *
764 * @param device The device to check
765 * @return one of OPTIONAL_CODECS_SUPPORT_UNKNOWN, OPTIONAL_CODECS_NOT_SUPPORTED, or
766 * OPTIONAL_CODECS_SUPPORTED.
767 * @hide
768 */
769 @UnsupportedAppUsage
770 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
771 @OptionalCodecsSupportStatus
772 public int isOptionalCodecsSupported(@NonNull BluetoothDevice device) {
773 verifyDeviceNotNull(device, "isOptionalCodecsSupported");
774 try {
775 final IBluetoothA2dp service = getService();
776 if (service != null && isEnabled() && isValidDevice(device)) {
777 return service.supportsOptionalCodecs(device);
778 }
779 if (service == null) Log.w(TAG, "Proxy not attached to service");
780 return OPTIONAL_CODECS_SUPPORT_UNKNOWN;
781 } catch (RemoteException e) {
782 Log.e(TAG, "Error talking to BT service in supportsOptionalCodecs()", e);
783 return OPTIONAL_CODECS_SUPPORT_UNKNOWN;
784 }
785 }
786
787 /**
788 * Returns whether this device should have optional codecs enabled.
789 *
790 * @param device The device in question.
791 * @return one of OPTIONAL_CODECS_PREF_UNKNOWN, OPTIONAL_CODECS_PREF_ENABLED, or
792 * OPTIONAL_CODECS_PREF_DISABLED.
793 * @hide
794 */
795 @UnsupportedAppUsage
796 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
797 @OptionalCodecsPreferenceStatus
798 public int isOptionalCodecsEnabled(@NonNull BluetoothDevice device) {
799 verifyDeviceNotNull(device, "isOptionalCodecsEnabled");
800 try {
801 final IBluetoothA2dp service = getService();
802 if (service != null && isEnabled() && isValidDevice(device)) {
803 return service.getOptionalCodecsEnabled(device);
804 }
805 if (service == null) Log.w(TAG, "Proxy not attached to service");
806 return OPTIONAL_CODECS_PREF_UNKNOWN;
807 } catch (RemoteException e) {
808 Log.e(TAG, "Error talking to BT service in getOptionalCodecsEnabled()", e);
809 return OPTIONAL_CODECS_PREF_UNKNOWN;
810 }
811 }
812
813 /**
814 * Sets a persistent preference for whether a given device should have optional codecs enabled.
815 *
816 * @param device The device to set this preference for.
817 * @param value Whether the optional codecs should be enabled for this device. This should be
818 * one of OPTIONAL_CODECS_PREF_UNKNOWN, OPTIONAL_CODECS_PREF_ENABLED, or
819 * OPTIONAL_CODECS_PREF_DISABLED.
820 * @hide
821 */
822 @UnsupportedAppUsage
823 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
824 public void setOptionalCodecsEnabled(@NonNull BluetoothDevice device,
825 @OptionalCodecsPreferenceStatus int value) {
826 verifyDeviceNotNull(device, "setOptionalCodecsEnabled");
827 try {
828 if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN
829 && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED
830 && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
831 Log.e(TAG, "Invalid value passed to setOptionalCodecsEnabled: " + value);
832 return;
833 }
834 final IBluetoothA2dp service = getService();
835 if (service != null && isEnabled()
836 && isValidDevice(device)) {
837 service.setOptionalCodecsEnabled(device, value);
838 }
839 if (service == null) Log.w(TAG, "Proxy not attached to service");
840 return;
841 } catch (RemoteException e) {
842 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
843 return;
844 }
845 }
846
847 /**
848 * Helper for converting a state to a string.
849 *
850 * For debug use only - strings are not internationalized.
851 *
852 * @hide
853 */
854 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
855 public static String stateToString(int state) {
856 switch (state) {
857 case STATE_DISCONNECTED:
858 return "disconnected";
859 case STATE_CONNECTING:
860 return "connecting";
861 case STATE_CONNECTED:
862 return "connected";
863 case STATE_DISCONNECTING:
864 return "disconnecting";
865 case STATE_PLAYING:
866 return "playing";
867 case STATE_NOT_PLAYING:
868 return "not playing";
869 default:
870 return "<unknown state " + state + ">";
871 }
872 }
873
874 private boolean isEnabled() {
875 if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
876 return false;
877 }
878
879 private void verifyDeviceNotNull(BluetoothDevice device, String methodName) {
880 if (device == null) {
881 Log.e(TAG, methodName + ": device param is null");
882 throw new IllegalArgumentException("Device cannot be null");
883 }
884 }
885
886 private boolean isValidDevice(BluetoothDevice device) {
887 if (device == null) return false;
888
889 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
890 return false;
891 }
892
893 private static void log(String msg) {
894 Log.d(TAG, msg);
895 }
896}