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