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 android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.RequiresPermission; 23 import android.annotation.SdkConstant; 24 import android.annotation.SdkConstant.SdkConstantType; 25 import android.annotation.SuppressLint; 26 import android.annotation.SystemApi; 27 import android.bluetooth.annotations.RequiresBluetoothConnectPermission; 28 import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; 29 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; 30 import android.compat.annotation.UnsupportedAppUsage; 31 import android.content.AttributionSource; 32 import android.content.Context; 33 import android.os.Build; 34 import android.os.IBinder; 35 import android.os.RemoteException; 36 import android.util.Log; 37 38 import java.lang.annotation.Retention; 39 import java.lang.annotation.RetentionPolicy; 40 import java.util.Collections; 41 import java.util.List; 42 import java.util.Objects; 43 44 /** 45 * Public API for controlling the Bluetooth Headset Service. This includes both Bluetooth Headset 46 * and Handsfree (v1.5) profiles. 47 * 48 * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset Service via IPC. 49 * 50 * <p>Use {@link BluetoothAdapter#getProfileProxy} to get the BluetoothHeadset proxy object. Use 51 * {@link BluetoothAdapter#closeProfileProxy} to close the service connection. 52 * 53 * <p>Android only supports one connected Bluetooth Headset at a time. Each method is protected with 54 * its appropriate permission. 55 */ 56 public final class BluetoothHeadset implements BluetoothProfile { 57 private static final String TAG = "BluetoothHeadset"; 58 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 59 private static final boolean VDBG = false; 60 61 /** 62 * Intent used to broadcast the change in connection state of the Headset profile. 63 * 64 * <p>This intent will have 3 extras: 65 * 66 * <ul> 67 * <li>{@link #EXTRA_STATE} - The current state of the profile. 68 * <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. 69 * <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device. 70 * </ul> 71 * 72 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link 73 * #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link 74 * #STATE_DISCONNECTING}. 75 */ 76 @RequiresLegacyBluetoothPermission 77 @RequiresBluetoothConnectPermission 78 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 79 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 80 public static final String ACTION_CONNECTION_STATE_CHANGED = 81 "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED"; 82 83 /** 84 * Intent used to broadcast the change in the Audio Connection state of the HFP profile. 85 * 86 * <p>This intent will have 3 extras: 87 * 88 * <ul> 89 * <li>{@link #EXTRA_STATE} - The current state of the profile. 90 * <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. 91 * <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device. 92 * </ul> 93 * 94 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link 95 * #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED}, 96 */ 97 @RequiresLegacyBluetoothPermission 98 @RequiresBluetoothConnectPermission 99 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 100 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 101 public static final String ACTION_AUDIO_STATE_CHANGED = 102 "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED"; 103 104 /** 105 * Intent used to broadcast the selection of a connected device as active. 106 * 107 * <p>This intent will have one extra: 108 * 109 * <ul> 110 * <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can be null if no device 111 * is active. 112 * </ul> 113 * 114 * @hide 115 */ 116 @SystemApi 117 @RequiresLegacyBluetoothPermission 118 @RequiresBluetoothConnectPermission 119 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 120 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 121 @SuppressLint("ActionValue") 122 public static final String ACTION_ACTIVE_DEVICE_CHANGED = 123 "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED"; 124 125 /** 126 * Intent used to broadcast that the headset has posted a vendor-specific event. 127 * 128 * <p>This intent will have 4 extras and 1 category. 129 * 130 * <ul> 131 * <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device 132 * <li>{@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor specific command 133 * <li>{@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT command type which can 134 * be one of {@link #AT_CMD_TYPE_READ}, {@link #AT_CMD_TYPE_TEST}, or {@link 135 * #AT_CMD_TYPE_SET}, {@link #AT_CMD_TYPE_BASIC},{@link #AT_CMD_TYPE_ACTION}. 136 * <li>{@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command arguments. 137 * </ul> 138 * 139 * <p>The category is the Company ID of the vendor defining the vendor-specific command. {@link 140 * BluetoothAssignedNumbers} 141 * 142 * <p>For example, for Plantronics specific events Category will be {@link 143 * #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55 144 * 145 * <p>For example, an AT+XEVENT=foo,3 will get translated into 146 * 147 * <ul> 148 * <li>EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT 149 * <li>EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET 150 * <li>EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 151 * </ul> 152 */ 153 @RequiresLegacyBluetoothPermission 154 @RequiresBluetoothConnectPermission 155 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 156 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 157 public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT = 158 "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT"; 159 160 /** 161 * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains 162 * the name of the vendor-specific command. 163 */ 164 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = 165 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD"; 166 167 /** 168 * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains the 169 * AT command type of the vendor-specific command. 170 */ 171 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = 172 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE"; 173 174 /** 175 * AT command type READ used with {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} For 176 * example, AT+VGM?. There are no arguments for this command type. 177 */ 178 public static final int AT_CMD_TYPE_READ = 0; 179 180 /** 181 * AT command type TEST used with {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} For 182 * example, AT+VGM=?. There are no arguments for this command type. 183 */ 184 public static final int AT_CMD_TYPE_TEST = 1; 185 186 /** 187 * AT command type SET used with {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} For 188 * example, AT+VGM=<args>. 189 */ 190 public static final int AT_CMD_TYPE_SET = 2; 191 192 /** 193 * AT command type BASIC used with {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} For 194 * example, ATD. Single character commands and everything following the character are arguments. 195 */ 196 public static final int AT_CMD_TYPE_BASIC = 3; 197 198 /** 199 * AT command type ACTION used with {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} For 200 * example, AT+CHUP. There are no arguments for action commands. 201 */ 202 public static final int AT_CMD_TYPE_ACTION = 4; 203 204 /** 205 * A Parcelable String array extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} 206 * intents that contains the arguments to the vendor-specific command. 207 */ 208 public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = 209 "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS"; 210 211 /** 212 * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} for the 213 * companyId 214 */ 215 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY = 216 "android.bluetooth.headset.intent.category.companyid"; 217 218 /** A vendor-specific command for unsolicited result code. */ 219 public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID"; 220 221 /** 222 * A vendor-specific AT command 223 * 224 * @hide 225 */ 226 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XAPL = "+XAPL"; 227 228 /** 229 * A vendor-specific AT command 230 * 231 * @hide 232 */ 233 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV = "+IPHONEACCEV"; 234 235 /** 236 * Battery level indicator associated with {@link #VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV} 237 * 238 * @hide 239 */ 240 public static final int VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL = 1; 241 242 /** 243 * A vendor-specific AT command 244 * 245 * @hide 246 */ 247 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT = "+XEVENT"; 248 249 /** 250 * Battery level indicator associated with {@link #VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT} 251 * 252 * @hide 253 */ 254 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL = "BATTERY"; 255 256 /** 257 * A vendor-specific AT command that asks for the information about device manufacturer. 258 * 259 * @hide 260 */ 261 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_CGMI = "+CGMI"; 262 263 /** 264 * A vendor-specific AT command that asks for the information about the model of the device. 265 * 266 * @hide 267 */ 268 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_CGMM = "+CGMM"; 269 270 /** 271 * A vendor-specific AT command that asks for the revision information, for Android we will 272 * return the OS version and build number. 273 * 274 * @hide 275 */ 276 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_CGMR = "+CGMR"; 277 278 /** 279 * A vendor-specific AT command that asks for the device's serial number. 280 * 281 * @hide 282 */ 283 public static final String VENDOR_SPECIFIC_HEADSET_EVENT_CGSN = "+CGSN"; 284 285 /** 286 * Headset state when SCO audio is not connected. This state can be one of {@link #EXTRA_STATE} 287 * or {@link #EXTRA_PREVIOUS_STATE} of {@link #ACTION_AUDIO_STATE_CHANGED} intent. 288 */ 289 public static final int STATE_AUDIO_DISCONNECTED = 10; 290 291 /** 292 * Headset state when SCO audio is connecting. This state can be one of {@link #EXTRA_STATE} or 293 * {@link #EXTRA_PREVIOUS_STATE} of {@link #ACTION_AUDIO_STATE_CHANGED} intent. 294 */ 295 public static final int STATE_AUDIO_CONNECTING = 11; 296 297 /** 298 * Headset state when SCO audio is connected. This state can be one of {@link #EXTRA_STATE} or 299 * {@link #EXTRA_PREVIOUS_STATE} of {@link #ACTION_AUDIO_STATE_CHANGED} intent. 300 */ 301 public static final int STATE_AUDIO_CONNECTED = 12; 302 303 /** 304 * Intent used to broadcast the headset's indicator status 305 * 306 * <p>This intent will have 3 extras: 307 * 308 * <ul> 309 * <li>{@link #EXTRA_HF_INDICATORS_IND_ID} - The Assigned number of headset Indicator which is 310 * supported by the headset ( as indicated by AT+BIND command in the SLC sequence) or 311 * whose value is changed (indicated by AT+BIEV command) 312 * <li>{@link #EXTRA_HF_INDICATORS_IND_VALUE} - Updated value of headset indicator. 313 * <li>{@link BluetoothDevice#EXTRA_DEVICE} - Remote device. 314 * </ul> 315 * 316 * <p>{@link #EXTRA_HF_INDICATORS_IND_ID} is defined by Bluetooth SIG and each of the indicators 317 * are given an assigned number. Below shows the assigned number of Indicator added so far - 318 * Enhanced Safety - 1, Valid Values: 0 - Disabled, 1 - Enabled - Battery Level - 2, Valid 319 * Values: 0~100 - Remaining level of Battery 320 * 321 * @hide 322 */ 323 @RequiresLegacyBluetoothPermission 324 @RequiresBluetoothConnectPermission 325 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 326 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 327 public static final String ACTION_HF_INDICATORS_VALUE_CHANGED = 328 "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED"; 329 330 /** 331 * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED} intents that contains the 332 * assigned number of the headset indicator as defined by Bluetooth SIG that is being sent. 333 * Value range is 0-65535 as defined in HFP 1.7 334 * 335 * @hide 336 */ 337 public static final String EXTRA_HF_INDICATORS_IND_ID = 338 "android.bluetooth.headset.extra.HF_INDICATORS_IND_ID"; 339 340 /** 341 * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED} intents that contains the 342 * value of the Headset indicator that is being sent. 343 * 344 * @hide 345 */ 346 public static final String EXTRA_HF_INDICATORS_IND_VALUE = 347 "android.bluetooth.headset.extra.HF_INDICATORS_IND_VALUE"; 348 349 private final BluetoothAdapter mAdapter; 350 private final AttributionSource mAttributionSource; 351 352 private IBluetoothHeadset mService; 353 354 /** Create a BluetoothHeadset proxy object. */ BluetoothHeadset(Context context, BluetoothAdapter adapter)355 /* package */ BluetoothHeadset(Context context, BluetoothAdapter adapter) { 356 mAdapter = adapter; 357 mAttributionSource = adapter.getAttributionSource(); 358 mService = null; 359 } 360 361 /** 362 * Close the connection to the backing service. Other public functions of BluetoothHeadset will 363 * return default error results once close() has been called. Multiple invocations of close() 364 * are ok. 365 * 366 * @hide 367 */ 368 @UnsupportedAppUsage close()369 public void close() { 370 mAdapter.closeProfileProxy(this); 371 } 372 373 /** @hide */ 374 @Override onServiceConnected(IBinder service)375 public void onServiceConnected(IBinder service) { 376 mService = IBluetoothHeadset.Stub.asInterface(service); 377 } 378 379 /** @hide */ 380 @Override onServiceDisconnected()381 public void onServiceDisconnected() { 382 mService = null; 383 } 384 getService()385 private IBluetoothHeadset getService() { 386 return mService; 387 } 388 389 /** @hide */ 390 @Override getAdapter()391 public BluetoothAdapter getAdapter() { 392 return mAdapter; 393 } 394 395 /** @hide */ 396 @Override 397 @SuppressWarnings("Finalize") // empty finalize for api signature finalize()398 protected void finalize() throws Throwable { 399 // The empty finalize needs to be kept or the 400 // cts signature tests would fail. 401 } 402 403 /** 404 * Initiate connection to a profile of the remote bluetooth device. 405 * 406 * <p>Currently, the system supports only 1 connection to the headset/handsfree profile. The API 407 * will automatically disconnect connected devices before connecting. 408 * 409 * <p>This API returns false in scenarios like the profile on the device is already connected or 410 * Bluetooth is not turned on. When this API returns true, it is guaranteed that connection 411 * state intent for the profile will be broadcasted with the state. Users can get the connection 412 * state of the profile from this intent. 413 * 414 * @param device Remote Bluetooth Device 415 * @return false on immediate error, true otherwise 416 * @hide 417 */ 418 @SystemApi 419 @RequiresLegacyBluetoothAdminPermission 420 @RequiresBluetoothConnectPermission 421 @RequiresPermission( 422 allOf = { 423 android.Manifest.permission.BLUETOOTH_CONNECT, 424 android.Manifest.permission.MODIFY_PHONE_STATE, 425 }) connect(BluetoothDevice device)426 public boolean connect(BluetoothDevice device) { 427 if (DBG) log("connect(" + device + ")"); 428 final IBluetoothHeadset service = getService(); 429 if (service == null) { 430 Log.w(TAG, "Proxy not attached to service"); 431 if (DBG) log(Log.getStackTraceString(new Throwable())); 432 } else if (isEnabled() && isValidDevice(device)) { 433 try { 434 return service.connect(device, mAttributionSource); 435 } catch (RemoteException e) { 436 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 437 } 438 } 439 return false; 440 } 441 442 /** 443 * Initiate disconnection from a profile 444 * 445 * <p>This API will return false in scenarios like the profile on the Bluetooth device is not in 446 * connected state etc. When this API returns, true, it is guaranteed that the connection state 447 * change intent will be broadcasted with the state. Users can get the disconnection state of 448 * the profile from this intent. 449 * 450 * <p>If the disconnection is initiated by a remote device, the state will transition from 451 * {@link #STATE_CONNECTED} to {@link #STATE_DISCONNECTED}. If the disconnect is initiated by 452 * the host (local) device the state will transition from {@link #STATE_CONNECTED} to state 453 * {@link #STATE_DISCONNECTING} to state {@link #STATE_DISCONNECTED}. The transition to {@link 454 * #STATE_DISCONNECTING} can be used to distinguish between the two scenarios. 455 * 456 * @param device Remote Bluetooth Device 457 * @return false on immediate error, true otherwise 458 * @hide 459 */ 460 @SystemApi 461 @RequiresLegacyBluetoothAdminPermission 462 @RequiresBluetoothConnectPermission 463 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) disconnect(BluetoothDevice device)464 public boolean disconnect(BluetoothDevice device) { 465 if (DBG) log("disconnect(" + device + ")"); 466 final IBluetoothHeadset service = getService(); 467 if (service == null) { 468 Log.w(TAG, "Proxy not attached to service"); 469 if (DBG) log(Log.getStackTraceString(new Throwable())); 470 } else if (isEnabled() && isValidDevice(device)) { 471 try { 472 return service.disconnect(device, mAttributionSource); 473 } catch (RemoteException e) { 474 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 475 } 476 } 477 return false; 478 } 479 480 /** {@inheritDoc} */ 481 @Override 482 @RequiresBluetoothConnectPermission 483 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getConnectedDevices()484 public List<BluetoothDevice> getConnectedDevices() { 485 if (VDBG) log("getConnectedDevices()"); 486 final IBluetoothHeadset service = getService(); 487 if (service == null) { 488 Log.w(TAG, "Proxy not attached to service"); 489 if (DBG) log(Log.getStackTraceString(new Throwable())); 490 } else if (isEnabled()) { 491 try { 492 return Attributable.setAttributionSource( 493 service.getConnectedDevices(mAttributionSource), mAttributionSource); 494 } catch (RemoteException e) { 495 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 496 } 497 } 498 return Collections.emptyList(); 499 } 500 501 /** {@inheritDoc} */ 502 @Override 503 @RequiresBluetoothConnectPermission 504 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getDevicesMatchingConnectionStates(int[] states)505 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 506 if (VDBG) log("getDevicesMatchingStates()"); 507 final IBluetoothHeadset service = getService(); 508 if (service == null) { 509 Log.w(TAG, "Proxy not attached to service"); 510 if (DBG) log(Log.getStackTraceString(new Throwable())); 511 } else if (isEnabled()) { 512 try { 513 return Attributable.setAttributionSource( 514 service.getDevicesMatchingConnectionStates(states, mAttributionSource), 515 mAttributionSource); 516 } catch (RemoteException e) { 517 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 518 } 519 } 520 return Collections.emptyList(); 521 } 522 523 /** {@inheritDoc} */ 524 @Override 525 @RequiresBluetoothConnectPermission 526 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getConnectionState(BluetoothDevice device)527 public int getConnectionState(BluetoothDevice device) { 528 if (VDBG) log("getConnectionState(" + device + ")"); 529 final IBluetoothHeadset service = getService(); 530 if (service == null) { 531 Log.w(TAG, "Proxy not attached to service"); 532 if (DBG) log(Log.getStackTraceString(new Throwable())); 533 } else if (isEnabled() && isValidDevice(device)) { 534 try { 535 return service.getConnectionState(device, mAttributionSource); 536 } catch (RemoteException e) { 537 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 538 } 539 } 540 return BluetoothProfile.STATE_DISCONNECTED; 541 } 542 543 /** 544 * Set connection policy of the profile 545 * 546 * <p>The device should already be paired. Connection policy can be one of {@link 547 * #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, {@link 548 * #CONNECTION_POLICY_UNKNOWN} 549 * 550 * @param device Paired bluetooth device 551 * @param connectionPolicy is the connection policy to set to for this profile 552 * @return true if connectionPolicy is set, false on error 553 * @hide 554 */ 555 @SystemApi 556 @RequiresBluetoothConnectPermission 557 @RequiresPermission( 558 allOf = { 559 android.Manifest.permission.BLUETOOTH_CONNECT, 560 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 561 android.Manifest.permission.MODIFY_PHONE_STATE, 562 }) setConnectionPolicy( @onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)563 public boolean setConnectionPolicy( 564 @NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { 565 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); 566 final IBluetoothHeadset service = getService(); 567 if (service == null) { 568 Log.w(TAG, "Proxy not attached to service"); 569 if (DBG) log(Log.getStackTraceString(new Throwable())); 570 } else if (isEnabled() 571 && isValidDevice(device) 572 && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 573 || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { 574 try { 575 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); 576 } catch (RemoteException e) { 577 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 578 } 579 } 580 return false; 581 } 582 583 /** 584 * Get the priority of the profile. 585 * 586 * <p>The priority can be any of: {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF}, {@link 587 * #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} 588 * 589 * @param device Bluetooth device 590 * @return priority of the device 591 * @hide 592 */ 593 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 594 @RequiresLegacyBluetoothPermission 595 @RequiresBluetoothConnectPermission 596 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getPriority(BluetoothDevice device)597 public int getPriority(BluetoothDevice device) { 598 if (VDBG) log("getPriority(" + device + ")"); 599 return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); 600 } 601 602 /** 603 * Get the connection policy of the profile. 604 * 605 * <p>The connection policy can be any of: {@link #CONNECTION_POLICY_ALLOWED}, {@link 606 * #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} 607 * 608 * @param device Bluetooth device 609 * @return connection policy of the device 610 * @hide 611 */ 612 @SystemApi 613 @RequiresBluetoothConnectPermission 614 @RequiresPermission( 615 allOf = { 616 android.Manifest.permission.BLUETOOTH_CONNECT, 617 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 618 }) getConnectionPolicy(@onNull BluetoothDevice device)619 public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { 620 if (VDBG) log("getConnectionPolicy(" + device + ")"); 621 final IBluetoothHeadset service = getService(); 622 if (service == null) { 623 Log.w(TAG, "Proxy not attached to service"); 624 if (DBG) log(Log.getStackTraceString(new Throwable())); 625 } else if (isEnabled() && isValidDevice(device)) { 626 try { 627 return service.getConnectionPolicy(device, mAttributionSource); 628 } catch (RemoteException e) { 629 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 630 } 631 } 632 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 633 } 634 635 /** 636 * Checks whether the headset supports some form of noise reduction 637 * 638 * @param device Bluetooth device 639 * @return true if echo cancellation and/or noise reduction is supported, false otherwise 640 */ 641 @RequiresLegacyBluetoothPermission 642 @RequiresBluetoothConnectPermission 643 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) isNoiseReductionSupported(@onNull BluetoothDevice device)644 public boolean isNoiseReductionSupported(@NonNull BluetoothDevice device) { 645 if (DBG) log("isNoiseReductionSupported()"); 646 final IBluetoothHeadset service = getService(); 647 if (service == null) { 648 Log.w(TAG, "Proxy not attached to service"); 649 if (DBG) log(Log.getStackTraceString(new Throwable())); 650 } else if (isEnabled() && isValidDevice(device)) { 651 try { 652 return service.isNoiseReductionSupported(device, mAttributionSource); 653 } catch (RemoteException e) { 654 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 655 } 656 } 657 return false; 658 } 659 660 /** 661 * Checks whether the headset supports voice recognition 662 * 663 * @param device Bluetooth device 664 * @return true if voice recognition is supported, false otherwise 665 */ 666 @RequiresLegacyBluetoothPermission 667 @RequiresBluetoothConnectPermission 668 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) isVoiceRecognitionSupported(@onNull BluetoothDevice device)669 public boolean isVoiceRecognitionSupported(@NonNull BluetoothDevice device) { 670 if (DBG) log("isVoiceRecognitionSupported()"); 671 final IBluetoothHeadset service = getService(); 672 if (service == null) { 673 Log.w(TAG, "Proxy not attached to service"); 674 if (DBG) log(Log.getStackTraceString(new Throwable())); 675 } else if (isEnabled() && isValidDevice(device)) { 676 try { 677 return service.isVoiceRecognitionSupported(device, mAttributionSource); 678 } catch (RemoteException e) { 679 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 680 } 681 } 682 return false; 683 } 684 685 /** 686 * Start Bluetooth voice recognition. This methods sends the voice recognition AT command to the 687 * headset and establishes the audio connection. 688 * 689 * <p>Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. If this function returns true, 690 * this intent will be broadcasted with {@link #EXTRA_STATE} set to {@link 691 * #STATE_AUDIO_CONNECTING}. 692 * 693 * <p>{@link #EXTRA_STATE} will transition from {@link #STATE_AUDIO_CONNECTING} to {@link 694 * #STATE_AUDIO_CONNECTED} when audio connection is established and to {@link 695 * #STATE_AUDIO_DISCONNECTED} in case of failure to establish the audio connection. 696 * 697 * @param device Bluetooth headset 698 * @return false if there is no headset connected, or the connected headset doesn't support 699 * voice recognition, or voice recognition is already started, or audio channel is occupied, 700 * or on error, true otherwise 701 */ 702 @RequiresLegacyBluetoothPermission 703 @RequiresBluetoothConnectPermission 704 @RequiresPermission( 705 allOf = { 706 android.Manifest.permission.BLUETOOTH_CONNECT, 707 android.Manifest.permission.MODIFY_PHONE_STATE, 708 }) startVoiceRecognition(BluetoothDevice device)709 public boolean startVoiceRecognition(BluetoothDevice device) { 710 if (DBG) log("startVoiceRecognition()"); 711 final IBluetoothHeadset service = getService(); 712 if (service == null) { 713 Log.w(TAG, "Proxy not attached to service"); 714 if (DBG) log(Log.getStackTraceString(new Throwable())); 715 } else if (isEnabled() && isValidDevice(device)) { 716 try { 717 return service.startVoiceRecognition(device, mAttributionSource); 718 } catch (RemoteException e) { 719 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 720 } 721 } 722 return false; 723 } 724 725 /** 726 * Stop Bluetooth Voice Recognition mode, and shut down the Bluetooth audio path. 727 * 728 * <p>Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. If this function returns true, 729 * this intent will be broadcasted with {@link #EXTRA_STATE} set to {@link 730 * #STATE_AUDIO_DISCONNECTED}. 731 * 732 * @param device Bluetooth headset 733 * @return false if there is no headset connected, or voice recognition has not started, or 734 * voice recognition has ended on this headset, or on error, true otherwise 735 */ 736 @RequiresLegacyBluetoothPermission 737 @RequiresBluetoothConnectPermission 738 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) stopVoiceRecognition(BluetoothDevice device)739 public boolean stopVoiceRecognition(BluetoothDevice device) { 740 if (DBG) log("stopVoiceRecognition()"); 741 final IBluetoothHeadset service = getService(); 742 if (service == null) { 743 Log.w(TAG, "Proxy not attached to service"); 744 if (DBG) log(Log.getStackTraceString(new Throwable())); 745 } else if (isEnabled() && isValidDevice(device)) { 746 try { 747 return service.stopVoiceRecognition(device, mAttributionSource); 748 } catch (RemoteException e) { 749 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 750 } 751 } 752 return false; 753 } 754 755 /** 756 * Check if Bluetooth SCO audio is connected. 757 * 758 * @param device Bluetooth headset 759 * @return true if SCO is connected, false otherwise or on error 760 */ 761 @RequiresLegacyBluetoothPermission 762 @RequiresBluetoothConnectPermission 763 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) isAudioConnected(BluetoothDevice device)764 public boolean isAudioConnected(BluetoothDevice device) { 765 if (VDBG) log("isAudioConnected()"); 766 final IBluetoothHeadset service = getService(); 767 if (service == null) { 768 Log.w(TAG, "Proxy not attached to service"); 769 if (DBG) log(Log.getStackTraceString(new Throwable())); 770 } else if (isEnabled() && isValidDevice(device)) { 771 try { 772 return service.isAudioConnected(device, mAttributionSource); 773 } catch (RemoteException e) { 774 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 775 } 776 } 777 return false; 778 } 779 780 /** @hide */ 781 @Retention(RetentionPolicy.SOURCE) 782 @IntDef( 783 value = { 784 BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 785 BluetoothHeadset.STATE_AUDIO_CONNECTING, 786 BluetoothHeadset.STATE_AUDIO_CONNECTED, 787 BluetoothStatusCodes.ERROR_TIMEOUT 788 }) 789 public @interface GetAudioStateReturnValues {} 790 791 /** 792 * Get the current audio state of the Headset. 793 * 794 * @param device is the Bluetooth device for which the audio state is being queried 795 * @return the audio state of the device or an error code 796 * @throws NullPointerException if the device is null 797 * @hide 798 */ 799 @SystemApi 800 @RequiresBluetoothConnectPermission 801 @RequiresPermission( 802 allOf = { 803 android.Manifest.permission.BLUETOOTH_CONNECT, 804 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 805 }) getAudioState(@onNull BluetoothDevice device)806 public @GetAudioStateReturnValues int getAudioState(@NonNull BluetoothDevice device) { 807 if (VDBG) log("getAudioState"); 808 Objects.requireNonNull(device); 809 final IBluetoothHeadset service = getService(); 810 if (service == null) { 811 Log.w(TAG, "Proxy not attached to service"); 812 if (DBG) log(Log.getStackTraceString(new Throwable())); 813 } else if (!isDisabled()) { 814 try { 815 return service.getAudioState(device, mAttributionSource); 816 } catch (RemoteException e) { 817 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 818 throw e.rethrowAsRuntimeException(); 819 } 820 } 821 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 822 } 823 824 /** @hide */ 825 @Retention(RetentionPolicy.SOURCE) 826 @IntDef( 827 value = { 828 BluetoothStatusCodes.SUCCESS, 829 BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND, 830 BluetoothStatusCodes.ERROR_TIMEOUT, 831 BluetoothStatusCodes.ERROR_UNKNOWN, 832 }) 833 public @interface SetAudioRouteAllowedReturnValues {} 834 835 /** @hide */ 836 @Retention(RetentionPolicy.SOURCE) 837 @IntDef( 838 value = { 839 BluetoothStatusCodes.ALLOWED, 840 BluetoothStatusCodes.NOT_ALLOWED, 841 BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND, 842 BluetoothStatusCodes.ERROR_TIMEOUT, 843 BluetoothStatusCodes.ERROR_UNKNOWN, 844 }) 845 public @interface GetAudioRouteAllowedReturnValues {} 846 847 /** 848 * Sets whether audio routing is allowed. When set to {@code false}, the AG will not route any 849 * audio to the HF unless explicitly told to. This method should be used in cases where the SCO 850 * channel is shared between multiple profiles and must be delegated by a source knowledgeable. 851 * 852 * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise. 853 * @return {@link BluetoothStatusCodes#SUCCESS} upon successful setting, otherwise an error 854 * code. 855 * @hide 856 */ 857 @SystemApi 858 @RequiresBluetoothConnectPermission 859 @RequiresPermission( 860 allOf = { 861 android.Manifest.permission.BLUETOOTH_CONNECT, 862 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 863 }) setAudioRouteAllowed(boolean allowed)864 public @SetAudioRouteAllowedReturnValues int setAudioRouteAllowed(boolean allowed) { 865 if (VDBG) log("setAudioRouteAllowed"); 866 final IBluetoothHeadset service = getService(); 867 if (service == null) { 868 Log.w(TAG, "Proxy not attached to service"); 869 if (DBG) log(Log.getStackTraceString(new Throwable())); 870 return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND; 871 } else if (isEnabled()) { 872 try { 873 service.setAudioRouteAllowed(allowed, mAttributionSource); 874 return BluetoothStatusCodes.SUCCESS; 875 } catch (RemoteException e) { 876 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 877 throw e.rethrowAsRuntimeException(); 878 } 879 } 880 881 Log.e(TAG, "setAudioRouteAllowed: Bluetooth disabled, but profile service still bound"); 882 return BluetoothStatusCodes.ERROR_UNKNOWN; 883 } 884 885 /** 886 * @return {@link BluetoothStatusCodes#ALLOWED} if audio routing is allowed, {@link 887 * BluetoothStatusCodes#NOT_ALLOWED} if audio routing is not allowed, or an error code if an 888 * error occurs. see {@link #setAudioRouteAllowed(boolean)}. 889 * @hide 890 */ 891 @SystemApi 892 @RequiresBluetoothConnectPermission 893 @RequiresPermission( 894 allOf = { 895 android.Manifest.permission.BLUETOOTH_CONNECT, 896 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 897 }) getAudioRouteAllowed()898 public @GetAudioRouteAllowedReturnValues int getAudioRouteAllowed() { 899 if (VDBG) log("getAudioRouteAllowed"); 900 final IBluetoothHeadset service = getService(); 901 if (service == null) { 902 Log.w(TAG, "Proxy not attached to service"); 903 if (DBG) log(Log.getStackTraceString(new Throwable())); 904 return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND; 905 } else if (isEnabled()) { 906 try { 907 return service.getAudioRouteAllowed(mAttributionSource) 908 ? BluetoothStatusCodes.ALLOWED 909 : BluetoothStatusCodes.NOT_ALLOWED; 910 } catch (RemoteException e) { 911 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 912 throw e.rethrowAsRuntimeException(); 913 } 914 } 915 916 Log.e(TAG, "getAudioRouteAllowed: Bluetooth disabled, but profile service still bound"); 917 return BluetoothStatusCodes.ERROR_UNKNOWN; 918 } 919 920 /** 921 * Force SCO audio to be opened regardless any other restrictions 922 * 923 * @param forced Whether or not SCO audio connection should be forced: True to force SCO audio 924 * False to use SCO audio in normal manner 925 * @hide 926 */ 927 @RequiresBluetoothConnectPermission 928 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) setForceScoAudio(boolean forced)929 public void setForceScoAudio(boolean forced) { 930 if (VDBG) log("setForceScoAudio " + String.valueOf(forced)); 931 final IBluetoothHeadset service = getService(); 932 if (service == null) { 933 Log.w(TAG, "Proxy not attached to service"); 934 if (DBG) log(Log.getStackTraceString(new Throwable())); 935 } else if (isEnabled()) { 936 try { 937 service.setForceScoAudio(forced, mAttributionSource); 938 } catch (RemoteException e) { 939 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 940 } 941 } 942 } 943 944 /** @hide */ 945 @Retention(RetentionPolicy.SOURCE) 946 @IntDef( 947 value = { 948 BluetoothStatusCodes.SUCCESS, 949 BluetoothStatusCodes.ERROR_UNKNOWN, 950 BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND, 951 BluetoothStatusCodes.ERROR_TIMEOUT, 952 BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_CONNECTED, 953 BluetoothStatusCodes.ERROR_NO_ACTIVE_DEVICES, 954 BluetoothStatusCodes.ERROR_NOT_ACTIVE_DEVICE, 955 BluetoothStatusCodes.ERROR_AUDIO_ROUTE_BLOCKED, 956 BluetoothStatusCodes.ERROR_CALL_ACTIVE, 957 BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED 958 }) 959 public @interface ConnectAudioReturnValues {} 960 961 /** 962 * Initiates a connection of SCO audio to the current active HFP device. The active HFP device 963 * can be identified with {@link BluetoothAdapter#getActiveDevices(int)}. 964 * 965 * <p>If this function returns {@link BluetoothStatusCodes#SUCCESS}, the intent {@link 966 * #ACTION_AUDIO_STATE_CHANGED} will be broadcasted twice. First with {@link #EXTRA_STATE} set 967 * to {@link #STATE_AUDIO_CONNECTING}. This will be followed by a broadcast with {@link 968 * #EXTRA_STATE} set to either {@link #STATE_AUDIO_CONNECTED} if the audio connection is 969 * established or {@link #STATE_AUDIO_DISCONNECTED} if there was a failure in establishing the 970 * audio connection. 971 * 972 * @return whether the connection was successfully initiated or an error code on failure 973 * @hide 974 */ 975 @SystemApi 976 @RequiresBluetoothConnectPermission 977 @RequiresPermission( 978 allOf = { 979 android.Manifest.permission.BLUETOOTH_CONNECT, 980 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 981 }) connectAudio()982 public @ConnectAudioReturnValues int connectAudio() { 983 if (VDBG) log("connectAudio()"); 984 final IBluetoothHeadset service = getService(); 985 if (service == null) { 986 Log.w(TAG, "Proxy not attached to service"); 987 if (DBG) log(Log.getStackTraceString(new Throwable())); 988 return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND; 989 } else if (isEnabled()) { 990 try { 991 return service.connectAudio(mAttributionSource); 992 } catch (RemoteException e) { 993 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 994 throw e.rethrowAsRuntimeException(); 995 } 996 } 997 998 Log.e(TAG, "connectAudio: Bluetooth disabled, but profile service still bound"); 999 return BluetoothStatusCodes.ERROR_UNKNOWN; 1000 } 1001 1002 /** @hide */ 1003 @Retention(RetentionPolicy.SOURCE) 1004 @IntDef( 1005 value = { 1006 BluetoothStatusCodes.SUCCESS, 1007 BluetoothStatusCodes.ERROR_UNKNOWN, 1008 BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND, 1009 BluetoothStatusCodes.ERROR_TIMEOUT, 1010 BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED, 1011 BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_DISCONNECTED 1012 }) 1013 public @interface DisconnectAudioReturnValues {} 1014 1015 /** 1016 * Initiates a disconnection of HFP SCO audio from actively connected devices. It also tears 1017 * down voice recognition or virtual voice call, if any exists. 1018 * 1019 * <p>If this function returns {@link BluetoothStatusCodes#SUCCESS}, the intent {@link 1020 * #ACTION_AUDIO_STATE_CHANGED} will be broadcasted with {@link #EXTRA_STATE} set to {@link 1021 * #STATE_AUDIO_DISCONNECTED}. 1022 * 1023 * @return whether the disconnection was initiated successfully or an error code on failure 1024 * @hide 1025 */ 1026 @SystemApi 1027 @RequiresBluetoothConnectPermission 1028 @RequiresPermission( 1029 allOf = { 1030 android.Manifest.permission.BLUETOOTH_CONNECT, 1031 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 1032 }) disconnectAudio()1033 public @DisconnectAudioReturnValues int disconnectAudio() { 1034 if (VDBG) log("disconnectAudio()"); 1035 final IBluetoothHeadset service = getService(); 1036 if (service == null) { 1037 Log.w(TAG, "Proxy not attached to service"); 1038 if (DBG) log(Log.getStackTraceString(new Throwable())); 1039 return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND; 1040 } else if (isEnabled()) { 1041 try { 1042 return service.disconnectAudio(mAttributionSource); 1043 } catch (RemoteException e) { 1044 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1045 throw e.rethrowAsRuntimeException(); 1046 } 1047 } 1048 1049 Log.e(TAG, "disconnectAudio: Bluetooth disabled, but profile service still bound"); 1050 return BluetoothStatusCodes.ERROR_UNKNOWN; 1051 } 1052 1053 /** 1054 * Initiates a SCO channel connection as a virtual voice call to the current active device 1055 * Active handsfree device will be notified of incoming call and connected call. 1056 * 1057 * <p>Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. If this function returns true, 1058 * this intent will be broadcasted with {@link #EXTRA_STATE} set to {@link 1059 * #STATE_AUDIO_CONNECTING}. 1060 * 1061 * <p>{@link #EXTRA_STATE} will transition from {@link #STATE_AUDIO_CONNECTING} to {@link 1062 * #STATE_AUDIO_CONNECTED} when audio connection is established and to {@link 1063 * #STATE_AUDIO_DISCONNECTED} in case of failure to establish the audio connection. 1064 * 1065 * @return true if successful, false if one of the following case applies - SCO audio is not 1066 * idle (connecting or connected) - virtual call has already started - there is no active 1067 * device - a Telecom managed call is going on - binder is dead or Bluetooth is disabled or 1068 * other error 1069 * @hide 1070 */ 1071 @SystemApi 1072 @RequiresLegacyBluetoothAdminPermission 1073 @RequiresBluetoothConnectPermission 1074 @RequiresPermission( 1075 allOf = { 1076 android.Manifest.permission.BLUETOOTH_CONNECT, 1077 android.Manifest.permission.MODIFY_PHONE_STATE, 1078 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 1079 }) startScoUsingVirtualVoiceCall()1080 public boolean startScoUsingVirtualVoiceCall() { 1081 if (DBG) log("startScoUsingVirtualVoiceCall()"); 1082 final IBluetoothHeadset service = getService(); 1083 if (service == null) { 1084 Log.w(TAG, "Proxy not attached to service"); 1085 if (DBG) log(Log.getStackTraceString(new Throwable())); 1086 } else if (isEnabled()) { 1087 try { 1088 return service.startScoUsingVirtualVoiceCall(mAttributionSource); 1089 } catch (RemoteException e) { 1090 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1091 } 1092 } 1093 return false; 1094 } 1095 1096 /** 1097 * Terminates an ongoing SCO connection and the associated virtual call. 1098 * 1099 * <p>Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. If this function returns true, 1100 * this intent will be broadcasted with {@link #EXTRA_STATE} set to {@link 1101 * #STATE_AUDIO_DISCONNECTED}. 1102 * 1103 * @return true if successful, false if one of the following case applies - virtual voice call 1104 * is not started or has ended - binder is dead or Bluetooth is disabled or other error 1105 * @hide 1106 */ 1107 @SystemApi 1108 @RequiresLegacyBluetoothAdminPermission 1109 @RequiresBluetoothConnectPermission 1110 @RequiresPermission( 1111 allOf = { 1112 android.Manifest.permission.BLUETOOTH_CONNECT, 1113 android.Manifest.permission.MODIFY_PHONE_STATE, 1114 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 1115 }) stopScoUsingVirtualVoiceCall()1116 public boolean stopScoUsingVirtualVoiceCall() { 1117 if (DBG) log("stopScoUsingVirtualVoiceCall()"); 1118 final IBluetoothHeadset service = getService(); 1119 if (service == null) { 1120 Log.w(TAG, "Proxy not attached to service"); 1121 if (DBG) log(Log.getStackTraceString(new Throwable())); 1122 } else if (isEnabled()) { 1123 try { 1124 return service.stopScoUsingVirtualVoiceCall(mAttributionSource); 1125 } catch (RemoteException e) { 1126 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1127 } 1128 } 1129 return false; 1130 } 1131 1132 /** 1133 * Notify Headset of phone state change. This is a backdoor for phone app to call 1134 * BluetoothHeadset since there is currently not a good way to get precise call state change 1135 * outside of phone app. 1136 * 1137 * @hide 1138 */ 1139 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 1140 @RequiresBluetoothConnectPermission 1141 @RequiresPermission( 1142 allOf = { 1143 android.Manifest.permission.BLUETOOTH_CONNECT, 1144 android.Manifest.permission.MODIFY_PHONE_STATE, 1145 }) phoneStateChanged( int numActive, int numHeld, int callState, String number, int type, String name)1146 public void phoneStateChanged( 1147 int numActive, int numHeld, int callState, String number, int type, String name) { 1148 final IBluetoothHeadset service = getService(); 1149 if (service == null) { 1150 Log.w(TAG, "Proxy not attached to service"); 1151 if (DBG) log(Log.getStackTraceString(new Throwable())); 1152 } else if (isEnabled()) { 1153 try { 1154 service.phoneStateChanged( 1155 numActive, numHeld, callState, number, type, name, mAttributionSource); 1156 } catch (RemoteException e) { 1157 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1158 } 1159 } 1160 } 1161 1162 /** 1163 * Send Headset of CLCC response 1164 * 1165 * @hide 1166 */ 1167 @RequiresBluetoothConnectPermission 1168 @RequiresPermission( 1169 allOf = { 1170 android.Manifest.permission.BLUETOOTH_CONNECT, 1171 android.Manifest.permission.MODIFY_PHONE_STATE, 1172 }) clccResponse( int index, int direction, int status, int mode, boolean mpty, String number, int type)1173 public void clccResponse( 1174 int index, int direction, int status, int mode, boolean mpty, String number, int type) { 1175 final IBluetoothHeadset service = getService(); 1176 if (service == null) { 1177 Log.w(TAG, "Proxy not attached to service"); 1178 if (DBG) log(Log.getStackTraceString(new Throwable())); 1179 } else if (isEnabled()) { 1180 try { 1181 service.clccResponse( 1182 index, direction, status, mode, mpty, number, type, mAttributionSource); 1183 } catch (RemoteException e) { 1184 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1185 } 1186 } 1187 } 1188 1189 /** 1190 * Sends a vendor-specific unsolicited result code to the headset. 1191 * 1192 * <p>The actual string to be sent is <code>command + ": " + arg</code>. For example, if {@code 1193 * command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg} is {@code "0"}, the 1194 * string <code>"+ANDROID: 0"</code> will be sent. 1195 * 1196 * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}. 1197 * 1198 * @param device Bluetooth headset. 1199 * @param command A vendor-specific command. 1200 * @param arg The argument that will be attached to the command. 1201 * @return {@code false} if there is no headset connected, or if the command is not an allowed 1202 * vendor-specific unsolicited result code, or on error. {@code true} otherwise. 1203 * @throws IllegalArgumentException if {@code command} is {@code null}. 1204 */ 1205 @RequiresLegacyBluetoothPermission 1206 @RequiresBluetoothConnectPermission 1207 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) sendVendorSpecificResultCode( BluetoothDevice device, String command, String arg)1208 public boolean sendVendorSpecificResultCode( 1209 BluetoothDevice device, String command, String arg) { 1210 if (DBG) { 1211 log("sendVendorSpecificResultCode()"); 1212 } 1213 if (command == null) { 1214 throw new IllegalArgumentException("command is null"); 1215 } 1216 final IBluetoothHeadset service = getService(); 1217 if (service == null) { 1218 Log.w(TAG, "Proxy not attached to service"); 1219 if (DBG) log(Log.getStackTraceString(new Throwable())); 1220 } else if (isEnabled() && isValidDevice(device)) { 1221 try { 1222 return service.sendVendorSpecificResultCode( 1223 device, command, arg, mAttributionSource); 1224 } catch (RemoteException e) { 1225 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1226 } 1227 } 1228 return false; 1229 } 1230 1231 /** 1232 * Select a connected device as active. 1233 * 1234 * <p>The active device selection is per profile. An active device's purpose is 1235 * profile-specific. For example, in HFP and HSP profiles, it is the device used for phone call 1236 * audio. If a remote device is not connected, it cannot be selected as active. 1237 * 1238 * <p>This API returns false in scenarios like the profile on the device is not connected or 1239 * Bluetooth is not turned on. When this API returns true, it is guaranteed that the {@link 1240 * #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted with the active device. 1241 * 1242 * @param device Remote Bluetooth Device, could be null if phone call audio should not be 1243 * streamed to a headset 1244 * @return false on immediate error, true otherwise 1245 * @hide 1246 */ 1247 @RequiresLegacyBluetoothAdminPermission 1248 @RequiresBluetoothConnectPermission 1249 @RequiresPermission( 1250 allOf = { 1251 android.Manifest.permission.BLUETOOTH_CONNECT, 1252 android.Manifest.permission.MODIFY_PHONE_STATE, 1253 }) 1254 @UnsupportedAppUsage(trackingBug = 171933273) setActiveDevice(@ullable BluetoothDevice device)1255 public boolean setActiveDevice(@Nullable BluetoothDevice device) { 1256 if (DBG) { 1257 Log.d(TAG, "setActiveDevice: " + device); 1258 } 1259 final IBluetoothHeadset service = getService(); 1260 if (service == null) { 1261 Log.w(TAG, "Proxy not attached to service"); 1262 if (DBG) log(Log.getStackTraceString(new Throwable())); 1263 } else if (isEnabled() && (device == null || isValidDevice(device))) { 1264 try { 1265 return service.setActiveDevice(device, mAttributionSource); 1266 } catch (RemoteException e) { 1267 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1268 } 1269 } 1270 return false; 1271 } 1272 1273 /** 1274 * Get the connected device that is active. 1275 * 1276 * @return the connected device that is active or null if no device is active. 1277 * @hide 1278 */ 1279 @UnsupportedAppUsage(trackingBug = 171933273) 1280 @Nullable 1281 @RequiresLegacyBluetoothPermission 1282 @RequiresBluetoothConnectPermission 1283 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getActiveDevice()1284 public BluetoothDevice getActiveDevice() { 1285 if (VDBG) Log.d(TAG, "getActiveDevice"); 1286 final IBluetoothHeadset service = getService(); 1287 if (service == null) { 1288 Log.w(TAG, "Proxy not attached to service"); 1289 if (DBG) log(Log.getStackTraceString(new Throwable())); 1290 } else if (isEnabled()) { 1291 try { 1292 return Attributable.setAttributionSource( 1293 service.getActiveDevice(mAttributionSource), mAttributionSource); 1294 } catch (RemoteException e) { 1295 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1296 } 1297 } 1298 return null; 1299 } 1300 1301 /** 1302 * Check if in-band ringing is currently enabled. In-band ringing could be disabled during an 1303 * active connection. 1304 * 1305 * @return true if in-band ringing is enabled, false if in-band ringing is disabled 1306 * @hide 1307 */ 1308 @SystemApi 1309 @RequiresLegacyBluetoothPermission 1310 @RequiresBluetoothConnectPermission 1311 @RequiresPermission( 1312 allOf = { 1313 android.Manifest.permission.BLUETOOTH_CONNECT, 1314 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 1315 }) isInbandRingingEnabled()1316 public boolean isInbandRingingEnabled() { 1317 if (DBG) log("isInbandRingingEnabled()"); 1318 final IBluetoothHeadset service = getService(); 1319 if (service == null) { 1320 Log.w(TAG, "Proxy not attached to service"); 1321 if (DBG) log(Log.getStackTraceString(new Throwable())); 1322 } else if (isEnabled()) { 1323 try { 1324 return service.isInbandRingingEnabled(mAttributionSource); 1325 } catch (RemoteException e) { 1326 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1327 } 1328 } 1329 return false; 1330 } 1331 1332 @UnsupportedAppUsage isEnabled()1333 private boolean isEnabled() { 1334 return mAdapter.getState() == BluetoothAdapter.STATE_ON; 1335 } 1336 isDisabled()1337 private boolean isDisabled() { 1338 return mAdapter.getState() == BluetoothAdapter.STATE_OFF; 1339 } 1340 isValidDevice(BluetoothDevice device)1341 private static boolean isValidDevice(BluetoothDevice device) { 1342 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); 1343 } 1344 log(String msg)1345 private static void log(String msg) { 1346 Log.d(TAG, msg); 1347 } 1348 } 1349