• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.bluetooth;
18 
19 import static android.bluetooth.BluetoothUtils.getSyncTimeout;
20 
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.RequiresNoPermission;
25 import android.annotation.RequiresPermission;
26 import android.annotation.SdkConstant;
27 import android.annotation.SdkConstant.SdkConstantType;
28 import android.annotation.SuppressLint;
29 import android.annotation.SystemApi;
30 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
31 import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
32 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
33 import android.compat.annotation.UnsupportedAppUsage;
34 import android.content.AttributionSource;
35 import android.content.Context;
36 import android.os.Build;
37 import android.os.IBinder;
38 import android.os.ParcelUuid;
39 import android.os.RemoteException;
40 import android.util.Log;
41 
42 import com.android.modules.utils.SynchronousResultReceiver;
43 
44 import java.lang.annotation.Retention;
45 import java.lang.annotation.RetentionPolicy;
46 import java.util.ArrayList;
47 import java.util.List;
48 import java.util.concurrent.TimeoutException;
49 
50 
51 /**
52  * This class provides the public APIs to control the Bluetooth A2DP
53  * profile.
54  *
55  * <p>BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP
56  * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
57  * the BluetoothA2dp proxy object.
58  *
59  * <p> Android only supports one connected Bluetooth A2dp device at a time.
60  * Each method is protected with its appropriate permission.
61  */
62 public final class BluetoothA2dp implements BluetoothProfile {
63     private static final String TAG = "BluetoothA2dp";
64     private static final boolean DBG = true;
65     private static final boolean VDBG = false;
66 
67     /**
68      * Intent used to broadcast the change in connection state of the A2DP
69      * profile.
70      *
71      * <p>This intent will have 3 extras:
72      * <ul>
73      * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
74      * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
75      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
76      * </ul>
77      *
78      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
79      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
80      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
81      */
82     @RequiresLegacyBluetoothPermission
83     @RequiresBluetoothConnectPermission
84     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
85     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
86     public static final String ACTION_CONNECTION_STATE_CHANGED =
87             "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED";
88 
89     /**
90      * Intent used to broadcast the change in the Playing state of the A2DP
91      * profile.
92      *
93      * <p>This intent will have 3 extras:
94      * <ul>
95      * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
96      * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
97      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
98      * </ul>
99      *
100      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
101      * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING},
102      */
103     @RequiresLegacyBluetoothPermission
104     @RequiresBluetoothConnectPermission
105     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
106     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
107     public static final String ACTION_PLAYING_STATE_CHANGED =
108             "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED";
109 
110     /** @hide */
111     @RequiresBluetoothConnectPermission
112     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
113     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
114     public static final String ACTION_AVRCP_CONNECTION_STATE_CHANGED =
115             "android.bluetooth.a2dp.profile.action.AVRCP_CONNECTION_STATE_CHANGED";
116 
117     /**
118      * Intent used to broadcast the selection of a connected device as active.
119      *
120      * <p>This intent will have one extra:
121      * <ul>
122      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can
123      * be null if no device is active. </li>
124      * </ul>
125      *
126      * @hide
127      */
128     @SystemApi
129     @RequiresLegacyBluetoothPermission
130     @RequiresBluetoothConnectPermission
131     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
132     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
133     @SuppressLint("ActionValue")
134     public static final String ACTION_ACTIVE_DEVICE_CHANGED =
135             "android.bluetooth.a2dp.profile.action.ACTIVE_DEVICE_CHANGED";
136 
137     /**
138      * Intent used to broadcast the change in the Audio Codec state of the
139      * A2DP Source profile.
140      *
141      * <p>This intent will have 2 extras:
142      * <ul>
143      * <li> {@link BluetoothCodecStatus#EXTRA_CODEC_STATUS} - The codec status. </li>
144      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device if the device is currently
145      * connected, otherwise it is not included.</li>
146      * </ul>
147      *
148      * @hide
149      */
150     @SystemApi
151     @RequiresLegacyBluetoothPermission
152     @RequiresBluetoothConnectPermission
153     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
154     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
155     @SuppressLint("ActionValue")
156     public static final String ACTION_CODEC_CONFIG_CHANGED =
157             "android.bluetooth.a2dp.profile.action.CODEC_CONFIG_CHANGED";
158 
159     /**
160      * A2DP sink device is streaming music. This state can be one of
161      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
162      * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
163      */
164     public static final int STATE_PLAYING = 10;
165 
166     /**
167      * A2DP sink device is NOT streaming music. This state can be one of
168      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
169      * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
170      */
171     public static final int STATE_NOT_PLAYING = 11;
172 
173     /** @hide */
174     @IntDef(prefix = "OPTIONAL_CODECS_", value = {
175             OPTIONAL_CODECS_SUPPORT_UNKNOWN,
176             OPTIONAL_CODECS_NOT_SUPPORTED,
177             OPTIONAL_CODECS_SUPPORTED
178     })
179     @Retention(RetentionPolicy.SOURCE)
180     public @interface OptionalCodecsSupportStatus {}
181 
182     /**
183      * We don't have a stored preference for whether or not the given A2DP sink device supports
184      * optional codecs.
185      *
186      * @hide
187      */
188     @SystemApi
189     public static final int OPTIONAL_CODECS_SUPPORT_UNKNOWN = -1;
190 
191     /**
192      * The given A2DP sink device does not support optional codecs.
193      *
194      * @hide
195      */
196     @SystemApi
197     public static final int OPTIONAL_CODECS_NOT_SUPPORTED = 0;
198 
199     /**
200      * The given A2DP sink device does support optional codecs.
201      *
202      * @hide
203      */
204     @SystemApi
205     public static final int OPTIONAL_CODECS_SUPPORTED = 1;
206 
207     /** @hide */
208     @IntDef(prefix = "OPTIONAL_CODECS_PREF_", value = {
209             OPTIONAL_CODECS_PREF_UNKNOWN,
210             OPTIONAL_CODECS_PREF_DISABLED,
211             OPTIONAL_CODECS_PREF_ENABLED
212     })
213     @Retention(RetentionPolicy.SOURCE)
214     public @interface OptionalCodecsPreferenceStatus {}
215 
216     /**
217      * We don't have a stored preference for whether optional codecs should be enabled or
218      * disabled for the given A2DP device.
219      *
220      * @hide
221      */
222     @SystemApi
223     public static final int OPTIONAL_CODECS_PREF_UNKNOWN = -1;
224 
225     /**
226      * Optional codecs should be disabled for the given A2DP device.
227      *
228      * @hide
229      */
230     @SystemApi
231     public static final int OPTIONAL_CODECS_PREF_DISABLED = 0;
232 
233     /**
234      * Optional codecs should be enabled for the given A2DP device.
235      *
236      * @hide
237      */
238     @SystemApi
239     public static final int OPTIONAL_CODECS_PREF_ENABLED = 1;
240 
241     /** @hide */
242     @IntDef(prefix = "DYNAMIC_BUFFER_SUPPORT_", value = {
243             DYNAMIC_BUFFER_SUPPORT_NONE,
244             DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD,
245             DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING
246     })
247     @Retention(RetentionPolicy.SOURCE)
248     public @interface Type {}
249 
250     /**
251      * Indicates the supported type of Dynamic Audio Buffer is not supported.
252      *
253      * @hide
254      */
255     @SystemApi
256     public static final int DYNAMIC_BUFFER_SUPPORT_NONE = 0;
257 
258     /**
259      * Indicates the supported type of Dynamic Audio Buffer is A2DP offload.
260      *
261      * @hide
262      */
263     @SystemApi
264     public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD = 1;
265 
266     /**
267      * Indicates the supported type of Dynamic Audio Buffer is A2DP software encoding.
268      *
269      * @hide
270      */
271     @SystemApi
272     public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING = 2;
273 
274     private final BluetoothAdapter mAdapter;
275     private final AttributionSource mAttributionSource;
276     private final BluetoothProfileConnector<IBluetoothA2dp> mProfileConnector =
277             new BluetoothProfileConnector(this, BluetoothProfile.A2DP, "BluetoothA2dp",
278                     IBluetoothA2dp.class.getName()) {
279                 @Override
280                 public IBluetoothA2dp getServiceInterface(IBinder service) {
281                     return IBluetoothA2dp.Stub.asInterface(service);
282                 }
283     };
284 
285     /**
286      * Create a BluetoothA2dp proxy object for interacting with the local
287      * Bluetooth A2DP service.
288      */
BluetoothA2dp(Context context, ServiceListener listener, BluetoothAdapter adapter)289     /* package */ BluetoothA2dp(Context context, ServiceListener listener,
290             BluetoothAdapter adapter) {
291         mAdapter = adapter;
292         mAttributionSource = adapter.getAttributionSource();
293         mProfileConnector.connect(context, listener);
294     }
295 
296     @UnsupportedAppUsage
close()297     /*package*/ void close() {
298         mProfileConnector.disconnect();
299     }
300 
getService()301     private IBluetoothA2dp getService() {
302         return mProfileConnector.getService();
303     }
304 
305     @Override
finalize()306     public void finalize() {
307         // The empty finalize needs to be kept or the
308         // cts signature tests would fail.
309     }
310 
311     /**
312      * Initiate connection to a profile of the remote Bluetooth device.
313      *
314      * <p> This API returns false in scenarios like the profile on the
315      * device is already connected or Bluetooth is not turned on.
316      * When this API returns true, it is guaranteed that
317      * connection state intent for the profile will be broadcasted with
318      * the state. Users can get the connection state of the profile
319      * from this intent.
320      *
321      *
322      * @param device Remote Bluetooth Device
323      * @return false on immediate error, true otherwise
324      * @hide
325      */
326     @RequiresLegacyBluetoothAdminPermission
327     @RequiresBluetoothConnectPermission
328     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
329     @UnsupportedAppUsage
connect(BluetoothDevice device)330     public boolean connect(BluetoothDevice device) {
331         if (DBG) log("connect(" + device + ")");
332         final IBluetoothA2dp service = getService();
333         final boolean defaultValue = false;
334         if (service == null) {
335             Log.w(TAG, "Proxy not attached to service");
336             if (DBG) log(Log.getStackTraceString(new Throwable()));
337         } else if (isEnabled() && isValidDevice(device)) {
338             try {
339                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
340                 service.connectWithAttribution(device, mAttributionSource, recv);
341                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
342             } catch (RemoteException | TimeoutException e) {
343                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
344             }
345         }
346         return defaultValue;
347     }
348 
349     /**
350      * Initiate disconnection from a profile
351      *
352      * <p> This API will return false in scenarios like the profile on the
353      * Bluetooth device is not in connected state etc. When this API returns,
354      * true, it is guaranteed that the connection state change
355      * intent will be broadcasted with the state. Users can get the
356      * disconnection state of the profile from this intent.
357      *
358      * <p> If the disconnection is initiated by a remote device, the state
359      * will transition from {@link #STATE_CONNECTED} to
360      * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
361      * host (local) device the state will transition from
362      * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
363      * state {@link #STATE_DISCONNECTED}. The transition to
364      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
365      * two scenarios.
366      *
367      *
368      * @param device Remote Bluetooth Device
369      * @return false on immediate error, true otherwise
370      * @hide
371      */
372     @RequiresLegacyBluetoothAdminPermission
373     @RequiresBluetoothConnectPermission
374     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
375     @UnsupportedAppUsage
disconnect(BluetoothDevice device)376     public boolean disconnect(BluetoothDevice device) {
377         if (DBG) log("disconnect(" + device + ")");
378         final IBluetoothA2dp service = getService();
379         final boolean defaultValue = false;
380         if (service == null) {
381             Log.w(TAG, "Proxy not attached to service");
382             if (DBG) log(Log.getStackTraceString(new Throwable()));
383         } else if (isEnabled() && isValidDevice(device)) {
384             try {
385                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
386                 service.disconnectWithAttribution(device, mAttributionSource, recv);
387                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
388             } catch (RemoteException | TimeoutException e) {
389                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
390             }
391         }
392         return defaultValue;
393     }
394 
395     /**
396      * {@inheritDoc}
397      */
398     @Override
399     @RequiresBluetoothConnectPermission
400     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getConnectedDevices()401     public List<BluetoothDevice> getConnectedDevices() {
402         if (VDBG) log("getConnectedDevices()");
403         final IBluetoothA2dp service = getService();
404         final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
405         if (service == null) {
406             Log.w(TAG, "Proxy not attached to service");
407             if (DBG) log(Log.getStackTraceString(new Throwable()));
408         } else if (isEnabled()) {
409             try {
410                 final SynchronousResultReceiver<List<BluetoothDevice>> recv =
411                         SynchronousResultReceiver.get();
412                 service.getConnectedDevicesWithAttribution(mAttributionSource, recv);
413                 return Attributable.setAttributionSource(
414                         recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
415                         mAttributionSource);
416             } catch (RemoteException | TimeoutException e) {
417                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
418             }
419         }
420         return defaultValue;
421     }
422 
423     /**
424      * {@inheritDoc}
425      */
426     @Override
427     @RequiresBluetoothConnectPermission
428     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getDevicesMatchingConnectionStates(int[] states)429     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
430         if (VDBG) log("getDevicesMatchingStates()");
431         final IBluetoothA2dp service = getService();
432         final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
433         if (service == null) {
434             Log.w(TAG, "Proxy not attached to service");
435             if (DBG) log(Log.getStackTraceString(new Throwable()));
436         } else if (isEnabled()) {
437             try {
438                 final SynchronousResultReceiver<List<BluetoothDevice>> recv =
439                         SynchronousResultReceiver.get();
440                 service.getDevicesMatchingConnectionStatesWithAttribution(states,
441                         mAttributionSource, recv);
442                 return Attributable.setAttributionSource(
443                         recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
444                         mAttributionSource);
445             } catch (RemoteException | TimeoutException e) {
446                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
447             }
448         }
449         return defaultValue;
450     }
451 
452     /**
453      * {@inheritDoc}
454      */
455     @Override
456     @RequiresBluetoothConnectPermission
457     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getConnectionState(BluetoothDevice device)458     public @BtProfileState int getConnectionState(BluetoothDevice device) {
459         if (VDBG) log("getState(" + device + ")");
460         final IBluetoothA2dp service = getService();
461         final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
462         if (service == null) {
463             Log.w(TAG, "Proxy not attached to service");
464             if (DBG) log(Log.getStackTraceString(new Throwable()));
465         } else if (isEnabled() && isValidDevice(device)) {
466             try {
467                 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
468                 service.getConnectionStateWithAttribution(device, mAttributionSource, recv);
469                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
470             } catch (RemoteException | TimeoutException e) {
471                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
472             }
473         }
474         return defaultValue;
475     }
476 
477     /**
478      * Select a connected device as active.
479      *
480      * The active device selection is per profile. An active device's
481      * purpose is profile-specific. For example, A2DP audio streaming
482      * is to the active A2DP Sink device. If a remote device is not
483      * connected, it cannot be selected as active.
484      *
485      * <p> This API returns false in scenarios like the profile on the
486      * device is not connected or Bluetooth is not turned on.
487      * When this API returns true, it is guaranteed that the
488      * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
489      * with the active device.
490      *
491      * @param device the remote Bluetooth device. Could be null to clear
492      * the active device and stop streaming audio to a Bluetooth device.
493      * @return false on immediate error, true otherwise
494      * @hide
495      */
496     @RequiresLegacyBluetoothAdminPermission
497     @RequiresBluetoothConnectPermission
498     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
499     @UnsupportedAppUsage(trackingBug = 171933273)
setActiveDevice(@ullable BluetoothDevice device)500     public boolean setActiveDevice(@Nullable BluetoothDevice device) {
501         if (DBG) log("setActiveDevice(" + device + ")");
502         final IBluetoothA2dp service = getService();
503         final boolean defaultValue = false;
504         if (service == null) {
505             Log.w(TAG, "Proxy not attached to service");
506             if (DBG) log(Log.getStackTraceString(new Throwable()));
507         } else if (isEnabled() && ((device == null) || isValidDevice(device))) {
508             try {
509                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
510                 service.setActiveDevice(device, mAttributionSource, recv);
511                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
512             } catch (RemoteException | TimeoutException e) {
513                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
514             }
515         }
516         return defaultValue;
517     }
518 
519     /**
520      * Get the connected device that is active.
521      *
522      * @return the connected device that is active or null if no device
523      * is active
524      * @hide
525      */
526     @UnsupportedAppUsage(trackingBug = 171933273)
527     @Nullable
528     @RequiresLegacyBluetoothPermission
529     @RequiresBluetoothConnectPermission
530     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getActiveDevice()531     public BluetoothDevice getActiveDevice() {
532         if (VDBG) log("getActiveDevice()");
533         final IBluetoothA2dp service = getService();
534         final BluetoothDevice defaultValue = null;
535         if (service == null) {
536             Log.w(TAG, "Proxy not attached to service");
537             if (DBG) log(Log.getStackTraceString(new Throwable()));
538         } else if (isEnabled()) {
539             try {
540                 final SynchronousResultReceiver<BluetoothDevice> recv =
541                         SynchronousResultReceiver.get();
542                 service.getActiveDevice(mAttributionSource, recv);
543                 return Attributable.setAttributionSource(
544                         recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
545                         mAttributionSource);
546             } catch (RemoteException | TimeoutException e) {
547                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
548             }
549         }
550         return defaultValue;
551     }
552 
553     /**
554      * Set priority of the profile
555      *
556      * <p> The device should already be paired.
557      * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}
558      *
559      * @param device Paired bluetooth device
560      * @param priority
561      * @return true if priority is set, false on error
562      * @hide
563      */
564     @RequiresBluetoothConnectPermission
565     @RequiresPermission(allOf = {
566             android.Manifest.permission.BLUETOOTH_CONNECT,
567             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
568     })
setPriority(BluetoothDevice device, int priority)569     public boolean setPriority(BluetoothDevice device, int priority) {
570         if (DBG) log("setPriority(" + device + ", " + priority + ")");
571         return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
572     }
573 
574     /**
575      * Set connection policy of the profile
576      *
577      * <p> The device should already be paired.
578      * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
579      * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
580      *
581      * @param device Paired bluetooth device
582      * @param connectionPolicy is the connection policy to set to for this profile
583      * @return true if connectionPolicy is set, false on error
584      * @hide
585      */
586     @SystemApi
587     @RequiresBluetoothConnectPermission
588     @RequiresPermission(allOf = {
589             android.Manifest.permission.BLUETOOTH_CONNECT,
590             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
591     })
setConnectionPolicy(@onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)592     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
593             @ConnectionPolicy int connectionPolicy) {
594         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
595         final IBluetoothA2dp service = getService();
596         final boolean defaultValue = false;
597         if (service == null) {
598             Log.w(TAG, "Proxy not attached to service");
599             if (DBG) log(Log.getStackTraceString(new Throwable()));
600         } else if (isEnabled() && isValidDevice(device)
601                     && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
602                         || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
603             try {
604                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
605                 service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
606                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
607             } catch (RemoteException | TimeoutException e) {
608                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
609             }
610         }
611         return defaultValue;
612     }
613 
614     /**
615      * Get the priority of the profile.
616      *
617      * <p> The priority can be any of:
618      * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
619      *
620      * @param device Bluetooth device
621      * @return priority of the device
622      * @hide
623      */
624     @RequiresLegacyBluetoothPermission
625     @RequiresBluetoothConnectPermission
626     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
627     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
getPriority(BluetoothDevice device)628     public int getPriority(BluetoothDevice device) {
629         if (VDBG) log("getPriority(" + device + ")");
630         return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
631     }
632 
633     /**
634      * Get the connection policy of the profile.
635      *
636      * <p> The connection policy can be any of:
637      * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
638      * {@link #CONNECTION_POLICY_UNKNOWN}
639      *
640      * @param device Bluetooth device
641      * @return connection policy of the device
642      * @hide
643      */
644     @SystemApi
645     @RequiresBluetoothConnectPermission
646     @RequiresPermission(allOf = {
647             android.Manifest.permission.BLUETOOTH_CONNECT,
648             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
649     })
getConnectionPolicy(@onNull BluetoothDevice device)650     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
651         if (VDBG) log("getConnectionPolicy(" + device + ")");
652         final IBluetoothA2dp service = getService();
653         final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
654         if (service == null) {
655             Log.w(TAG, "Proxy not attached to service");
656             if (DBG) log(Log.getStackTraceString(new Throwable()));
657         } else if (isEnabled() && isValidDevice(device)) {
658             try {
659                 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
660                 service.getConnectionPolicy(device, mAttributionSource, recv);
661                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
662             } catch (RemoteException | TimeoutException e) {
663                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
664             }
665         }
666         return defaultValue;
667     }
668 
669     /**
670      * Checks if Avrcp device supports the absolute volume feature.
671      *
672      * @return true if device supports absolute volume
673      * @hide
674      */
675     @RequiresNoPermission
isAvrcpAbsoluteVolumeSupported()676     public boolean isAvrcpAbsoluteVolumeSupported() {
677         if (DBG) Log.d(TAG, "isAvrcpAbsoluteVolumeSupported");
678         final IBluetoothA2dp service = getService();
679         final boolean defaultValue = false;
680         if (service == null) {
681             Log.w(TAG, "Proxy not attached to service");
682             if (DBG) log(Log.getStackTraceString(new Throwable()));
683         } else if (isEnabled()) {
684             try {
685                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
686                 service.isAvrcpAbsoluteVolumeSupported(recv);
687                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
688             } catch (RemoteException | TimeoutException e) {
689                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
690             }
691         }
692         return defaultValue;
693     }
694 
695     /**
696      * Tells remote device to set an absolute volume. Only if absolute volume is supported
697      *
698      * @param volume Absolute volume to be set on AVRCP side
699      * @hide
700      */
701     @SystemApi
702     @RequiresBluetoothConnectPermission
703     @RequiresPermission(allOf = {
704             android.Manifest.permission.BLUETOOTH_CONNECT,
705             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
706     })
setAvrcpAbsoluteVolume(int volume)707     public void setAvrcpAbsoluteVolume(int volume) {
708         if (DBG) Log.d(TAG, "setAvrcpAbsoluteVolume");
709         final IBluetoothA2dp service = getService();
710         if (service == null) {
711             Log.w(TAG, "Proxy not attached to service");
712             if (DBG) log(Log.getStackTraceString(new Throwable()));
713         } else if (isEnabled()) {
714             try {
715                 service.setAvrcpAbsoluteVolume(volume, mAttributionSource);
716             } catch (RemoteException e) {
717                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
718             }
719         }
720     }
721 
722     /**
723      * Check if A2DP profile is streaming music.
724      *
725      * @param device BluetoothDevice device
726      */
727     @RequiresLegacyBluetoothPermission
728     @RequiresBluetoothConnectPermission
729     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
isA2dpPlaying(BluetoothDevice device)730     public boolean isA2dpPlaying(BluetoothDevice device) {
731         if (DBG) log("isA2dpPlaying(" + device + ")");
732         final IBluetoothA2dp service = getService();
733         final boolean defaultValue = false;
734         if (service == null) {
735             Log.w(TAG, "Proxy not attached to service");
736             if (DBG) log(Log.getStackTraceString(new Throwable()));
737         } else if (isEnabled() && isValidDevice(device)) {
738             try {
739                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
740                 service.isA2dpPlaying(device, mAttributionSource, recv);
741                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
742             } catch (RemoteException | TimeoutException e) {
743                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
744             }
745         }
746         return defaultValue;
747     }
748 
749     /**
750      * This function checks if the remote device is an AVCRP
751      * target and thus whether we should send volume keys
752      * changes or not.
753      *
754      * @hide
755      */
756     @RequiresBluetoothConnectPermission
757     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
shouldSendVolumeKeys(BluetoothDevice device)758     public boolean shouldSendVolumeKeys(BluetoothDevice device) {
759         if (isEnabled() && isValidDevice(device)) {
760             ParcelUuid[] uuids = device.getUuids();
761             if (uuids == null) return false;
762 
763             for (ParcelUuid uuid : uuids) {
764                 if (uuid.equals(BluetoothUuid.AVRCP_TARGET)) {
765                     return true;
766                 }
767             }
768         }
769         return false;
770     }
771 
772     /**
773      * Gets the current codec status (configuration and capability).
774      *
775      * @param device the remote Bluetooth device.
776      * @return the current codec status
777      * @hide
778      */
779     @SystemApi
780     @Nullable
781     @RequiresLegacyBluetoothPermission
782     @RequiresBluetoothConnectPermission
783     @RequiresPermission(allOf = {
784             android.Manifest.permission.BLUETOOTH_CONNECT,
785             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
786     })
getCodecStatus(@onNull BluetoothDevice device)787     public BluetoothCodecStatus getCodecStatus(@NonNull BluetoothDevice device) {
788         if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")");
789         verifyDeviceNotNull(device, "getCodecStatus");
790         final IBluetoothA2dp service = getService();
791         final BluetoothCodecStatus defaultValue = null;
792         if (service == null) {
793             Log.w(TAG, "Proxy not attached to service");
794             if (DBG) log(Log.getStackTraceString(new Throwable()));
795         } else if (isEnabled() && isValidDevice(device)) {
796             try {
797                 final SynchronousResultReceiver<BluetoothCodecStatus> recv =
798                         SynchronousResultReceiver.get();
799                 service.getCodecStatus(device, mAttributionSource, recv);
800                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
801             } catch (RemoteException | TimeoutException e) {
802                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
803             }
804         }
805         return defaultValue;
806     }
807 
808     /**
809      * Sets the codec configuration preference.
810      *
811      * @param device the remote Bluetooth device.
812      * @param codecConfig the codec configuration preference
813      * @hide
814      */
815     @SystemApi
816     @RequiresLegacyBluetoothPermission
817     @RequiresBluetoothConnectPermission
818     @RequiresPermission(allOf = {
819             android.Manifest.permission.BLUETOOTH_CONNECT,
820             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
821     })
setCodecConfigPreference(@onNull BluetoothDevice device, @NonNull BluetoothCodecConfig codecConfig)822     public void setCodecConfigPreference(@NonNull BluetoothDevice device,
823                                          @NonNull BluetoothCodecConfig codecConfig) {
824         if (DBG) Log.d(TAG, "setCodecConfigPreference(" + device + ")");
825         verifyDeviceNotNull(device, "setCodecConfigPreference");
826         if (codecConfig == null) {
827             Log.e(TAG, "setCodecConfigPreference: Codec config can't be null");
828             throw new IllegalArgumentException("codecConfig cannot be null");
829         }
830         final IBluetoothA2dp service = getService();
831         if (service == null) {
832             Log.w(TAG, "Proxy not attached to service");
833             if (DBG) log(Log.getStackTraceString(new Throwable()));
834         } else if (isEnabled()) {
835             try {
836                 service.setCodecConfigPreference(device, codecConfig, mAttributionSource);
837             } catch (RemoteException e) {
838                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
839             }
840         }
841     }
842 
843     /**
844      * Enables the optional codecs for the given device for this connection.
845      *
846      * If the given device supports another codec type than
847      * {@link BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC}, this will switch to it.
848      * Switching from one codec to another will create a short audio drop.
849      * In case of multiple applications calling the method, the last call will be taken into
850      * account, overriding any previous call
851      *
852      * See {@link #setOptionalCodecsEnabled} to enable optional codecs by default
853      * when the given device is connected.
854      *
855      * @param device the remote Bluetooth device
856      * @hide
857      */
858     @SystemApi
859     @RequiresLegacyBluetoothPermission
860     @RequiresBluetoothConnectPermission
861     @RequiresPermission(allOf = {
862             android.Manifest.permission.BLUETOOTH_CONNECT,
863             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
864     })
enableOptionalCodecs(@onNull BluetoothDevice device)865     public void enableOptionalCodecs(@NonNull BluetoothDevice device) {
866         if (DBG) Log.d(TAG, "enableOptionalCodecs(" + device + ")");
867         verifyDeviceNotNull(device, "enableOptionalCodecs");
868         enableDisableOptionalCodecs(device, true);
869     }
870 
871     /**
872      * Disables the optional codecs for the given device for this connection.
873      *
874      * When optional codecs are disabled, the device will use the default
875      * Bluetooth audio codec type.
876      * Switching from one codec to another will create a short audio drop.
877      * In case of multiple applications calling the method, the last call will be taken into
878      * account, overriding any previous call
879      *
880      * See {@link BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC}.
881      * See {@link #setOptionalCodecsEnabled} to disable optional codecs by default
882      * when the given device is connected.
883      *
884      * @param device the remote Bluetooth device
885      * @hide
886      */
887     @SystemApi
888     @RequiresLegacyBluetoothPermission
889     @RequiresBluetoothConnectPermission
890     @RequiresPermission(allOf = {
891             android.Manifest.permission.BLUETOOTH_CONNECT,
892             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
893     })
disableOptionalCodecs(@onNull BluetoothDevice device)894     public void disableOptionalCodecs(@NonNull BluetoothDevice device) {
895         if (DBG) Log.d(TAG, "disableOptionalCodecs(" + device + ")");
896         verifyDeviceNotNull(device, "disableOptionalCodecs");
897         enableDisableOptionalCodecs(device, false);
898     }
899 
900     /**
901      * Enables or disables the optional codecs.
902      *
903      * @param device the remote Bluetooth device.
904      * @param enable if true, enable the optional codecs, otherwise disable them
905      */
906     @RequiresPermission(allOf = {
907             android.Manifest.permission.BLUETOOTH_CONNECT,
908             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
909     })
enableDisableOptionalCodecs(BluetoothDevice device, boolean enable)910     private void enableDisableOptionalCodecs(BluetoothDevice device, boolean enable) {
911         final IBluetoothA2dp service = getService();
912         if (service == null) {
913             Log.w(TAG, "Proxy not attached to service");
914             if (DBG) log(Log.getStackTraceString(new Throwable()));
915         } else if (isEnabled() && isValidDevice(device)) {
916             try {
917                 if (enable) {
918                     service.enableOptionalCodecs(device, mAttributionSource);
919                 } else {
920                     service.disableOptionalCodecs(device, mAttributionSource);
921                 }
922             } catch (RemoteException e) {
923                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
924             }
925         }
926     }
927 
928     /**
929      * Returns whether this device supports optional codecs.
930      *
931      * @param device the remote Bluetooth device
932      * @return whether the optional codecs are supported or not, or
933      *         {@link #OPTIONAL_CODECS_SUPPORT_UNKNOWN} if the state
934      *         can't be retrieved.
935      * @hide
936      */
937     @SystemApi
938     @RequiresLegacyBluetoothAdminPermission
939     @RequiresBluetoothConnectPermission
940     @RequiresPermission(allOf = {
941             android.Manifest.permission.BLUETOOTH_CONNECT,
942             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
943     })
isOptionalCodecsSupported( @onNull BluetoothDevice device)944     public @OptionalCodecsSupportStatus int isOptionalCodecsSupported(
945             @NonNull BluetoothDevice device) {
946         if (DBG) log("isOptionalCodecsSupported(" + device + ")");
947         verifyDeviceNotNull(device, "isOptionalCodecsSupported");
948         final IBluetoothA2dp service = getService();
949         final int defaultValue = OPTIONAL_CODECS_SUPPORT_UNKNOWN;
950         if (service == null) {
951             Log.w(TAG, "Proxy not attached to service");
952             if (DBG) log(Log.getStackTraceString(new Throwable()));
953         } else if (isEnabled() && isValidDevice(device)) {
954             try {
955                 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
956                 service.isOptionalCodecsSupported(device, mAttributionSource, recv);
957                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
958             } catch (RemoteException | TimeoutException e) {
959                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
960             }
961         }
962         return defaultValue;
963     }
964 
965     /**
966      * Returns whether this device has its optional codecs enabled.
967      *
968      * @param device the remote Bluetooth device
969      * @return whether the optional codecs are enabled or not, or
970      *         {@link #OPTIONAL_CODECS_PREF_UNKNOWN} if the state
971      *         can't be retrieved.
972      * @hide
973      */
974     @SystemApi
975     @RequiresLegacyBluetoothAdminPermission
976     @RequiresBluetoothConnectPermission
977     @RequiresPermission(allOf = {
978             android.Manifest.permission.BLUETOOTH_CONNECT,
979             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
980     })
isOptionalCodecsEnabled( @onNull BluetoothDevice device)981     public @OptionalCodecsPreferenceStatus int isOptionalCodecsEnabled(
982             @NonNull BluetoothDevice device) {
983         if (DBG) log("isOptionalCodecsEnabled(" + device + ")");
984         verifyDeviceNotNull(device, "isOptionalCodecsEnabled");
985         final IBluetoothA2dp service = getService();
986         final int defaultValue = OPTIONAL_CODECS_PREF_UNKNOWN;
987         if (service == null) {
988             Log.w(TAG, "Proxy not attached to service");
989             if (DBG) log(Log.getStackTraceString(new Throwable()));
990         } else if (isEnabled() && isValidDevice(device)) {
991             try {
992                 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
993                 service.isOptionalCodecsEnabled(device, mAttributionSource, recv);
994                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
995             } catch (RemoteException | TimeoutException e) {
996                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
997             }
998         }
999         return defaultValue;
1000     }
1001 
1002     /**
1003      * Sets the default state of optional codecs for the given device.
1004      *
1005      * Automatically enables or disables the optional codecs for the given device when
1006      * connected.
1007      *
1008      * @param device the remote Bluetooth device
1009      * @param value whether the optional codecs should be enabled for this device
1010      * @hide
1011      */
1012     @SystemApi
1013     @RequiresLegacyBluetoothAdminPermission
1014     @RequiresBluetoothConnectPermission
1015     @RequiresPermission(allOf = {
1016             android.Manifest.permission.BLUETOOTH_CONNECT,
1017             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
1018     })
setOptionalCodecsEnabled(@onNull BluetoothDevice device, @OptionalCodecsPreferenceStatus int value)1019     public void setOptionalCodecsEnabled(@NonNull BluetoothDevice device,
1020             @OptionalCodecsPreferenceStatus int value) {
1021         if (DBG) log("setOptionalCodecsEnabled(" + device + ")");
1022         verifyDeviceNotNull(device, "setOptionalCodecsEnabled");
1023         if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN
1024                 && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED
1025                 && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
1026             Log.e(TAG, "Invalid value passed to setOptionalCodecsEnabled: " + value);
1027             return;
1028         }
1029         final IBluetoothA2dp service = getService();
1030         if (service == null) {
1031             Log.w(TAG, "Proxy not attached to service");
1032             if (DBG) log(Log.getStackTraceString(new Throwable()));
1033         } else if (isEnabled() && isValidDevice(device)) {
1034             try {
1035                 service.setOptionalCodecsEnabled(device, value, mAttributionSource);
1036             } catch (RemoteException e) {
1037                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1038             }
1039         }
1040     }
1041 
1042     /**
1043      * Get the supported type of the Dynamic Audio Buffer.
1044      * <p>Possible return values are
1045      * {@link #DYNAMIC_BUFFER_SUPPORT_NONE},
1046      * {@link #DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD},
1047      * {@link #DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING}.
1048      *
1049      * @return supported type of Dynamic Audio Buffer feature
1050      *
1051      * @hide
1052      */
1053     @SystemApi
1054     @RequiresBluetoothConnectPermission
1055     @RequiresPermission(allOf = {
1056             android.Manifest.permission.BLUETOOTH_CONNECT,
1057             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
1058     })
getDynamicBufferSupport()1059     public @Type int getDynamicBufferSupport() {
1060         if (VDBG) log("getDynamicBufferSupport()");
1061         final IBluetoothA2dp service = getService();
1062         final int defaultValue = DYNAMIC_BUFFER_SUPPORT_NONE;
1063         if (service == null) {
1064             Log.w(TAG, "Proxy not attached to service");
1065             if (DBG) log(Log.getStackTraceString(new Throwable()));
1066         } else if (isEnabled()) {
1067             try {
1068                 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
1069                 service.getDynamicBufferSupport(mAttributionSource, recv);
1070                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1071             } catch (RemoteException | TimeoutException e) {
1072                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1073             }
1074         }
1075         return defaultValue;
1076     }
1077 
1078     /**
1079      * Return the record of {@link BufferConstraints} object that
1080      * has the default/maximum/minimum audio buffer. This can be used to inform what the controller
1081      * has support for the audio buffer.
1082      *
1083      * @return a record with {@link BufferConstraints} or null if report is unavailable
1084      * or unsupported
1085      *
1086      * @hide
1087      */
1088     @SystemApi
1089     @RequiresBluetoothConnectPermission
1090     @RequiresPermission(allOf = {
1091             android.Manifest.permission.BLUETOOTH_CONNECT,
1092             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
1093     })
getBufferConstraints()1094     public @Nullable BufferConstraints getBufferConstraints() {
1095         if (VDBG) log("getBufferConstraints()");
1096         final IBluetoothA2dp service = getService();
1097         final BufferConstraints defaultValue = null;
1098         if (service == null) {
1099             Log.w(TAG, "Proxy not attached to service");
1100             if (DBG) log(Log.getStackTraceString(new Throwable()));
1101         } else if (isEnabled()) {
1102             try {
1103                 final SynchronousResultReceiver<BufferConstraints> recv =
1104                         SynchronousResultReceiver.get();
1105                 service.getBufferConstraints(mAttributionSource, recv);
1106                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1107             } catch (RemoteException | TimeoutException e) {
1108                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1109             }
1110         }
1111         return defaultValue;
1112     }
1113 
1114     /**
1115      * Set Dynamic Audio Buffer Size.
1116      *
1117      * @param codec audio codec
1118      * @param value buffer millis
1119      * @return true to indicate success, or false on immediate error
1120      *
1121      * @hide
1122      */
1123     @SystemApi
1124     @RequiresBluetoothConnectPermission
1125     @RequiresPermission(allOf = {
1126             android.Manifest.permission.BLUETOOTH_CONNECT,
1127             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
1128     })
setBufferLengthMillis(@luetoothCodecConfig.SourceCodecType int codec, int value)1129     public boolean setBufferLengthMillis(@BluetoothCodecConfig.SourceCodecType int codec,
1130             int value) {
1131         if (VDBG) log("setBufferLengthMillis(" + codec + ", " + value + ")");
1132         if (value < 0) {
1133             Log.e(TAG, "Trying to set audio buffer length to a negative value: " + value);
1134             return false;
1135         }
1136         final IBluetoothA2dp service = getService();
1137         final boolean defaultValue = false;
1138         if (service == null) {
1139             Log.w(TAG, "Proxy not attached to service");
1140             if (DBG) log(Log.getStackTraceString(new Throwable()));
1141         } else if (isEnabled()) {
1142             try {
1143                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
1144                 service.setBufferLengthMillis(codec, value, mAttributionSource, recv);
1145                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
1146             } catch (RemoteException | TimeoutException e) {
1147                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1148             }
1149         }
1150         return defaultValue;
1151     }
1152 
1153     /**
1154      * Helper for converting a state to a string.
1155      *
1156      * For debug use only - strings are not internationalized.
1157      *
1158      * @hide
1159      */
1160     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
stateToString(int state)1161     public static String stateToString(int state) {
1162         switch (state) {
1163             case STATE_DISCONNECTED:
1164                 return "disconnected";
1165             case STATE_CONNECTING:
1166                 return "connecting";
1167             case STATE_CONNECTED:
1168                 return "connected";
1169             case STATE_DISCONNECTING:
1170                 return "disconnecting";
1171             case STATE_PLAYING:
1172                 return "playing";
1173             case STATE_NOT_PLAYING:
1174                 return "not playing";
1175             default:
1176                 return "<unknown state " + state + ">";
1177         }
1178     }
1179 
isEnabled()1180     private boolean isEnabled() {
1181         if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
1182         return false;
1183     }
1184 
verifyDeviceNotNull(BluetoothDevice device, String methodName)1185     private void verifyDeviceNotNull(BluetoothDevice device, String methodName) {
1186         if (device == null) {
1187             Log.e(TAG, methodName + ": device param is null");
1188             throw new IllegalArgumentException("Device cannot be null");
1189         }
1190     }
1191 
isValidDevice(BluetoothDevice device)1192     private boolean isValidDevice(BluetoothDevice device) {
1193         if (device == null) return false;
1194 
1195         if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
1196         return false;
1197     }
1198 
log(String msg)1199     private static void log(String msg) {
1200         Log.d(TAG, msg);
1201     }
1202 }
1203