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