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