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