1 /* 2 * Copyright (C) 2014 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.NonNull; 20 import android.annotation.RequiresPermission; 21 import android.annotation.SdkConstant; 22 import android.annotation.SdkConstant.SdkConstantType; 23 import android.bluetooth.annotations.RequiresBluetoothConnectPermission; 24 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; 25 import android.compat.annotation.UnsupportedAppUsage; 26 import android.content.Attributable; 27 import android.content.AttributionSource; 28 import android.content.Context; 29 import android.os.Binder; 30 import android.os.Build; 31 import android.os.Bundle; 32 import android.os.IBinder; 33 import android.os.RemoteException; 34 import android.util.Log; 35 36 import java.util.ArrayList; 37 import java.util.List; 38 39 /** 40 * Public API to control Hands Free Profile (HFP role only). 41 * <p> 42 * This class defines methods that shall be used by application to manage profile 43 * connection, calls states and calls actions. 44 * <p> 45 * 46 * @hide 47 */ 48 public final class BluetoothHeadsetClient implements BluetoothProfile { 49 private static final String TAG = "BluetoothHeadsetClient"; 50 private static final boolean DBG = true; 51 private static final boolean VDBG = false; 52 53 /** 54 * Intent sent whenever connection to remote changes. 55 * 56 * <p>It includes two extras: 57 * <code>BluetoothProfile.EXTRA_PREVIOUS_STATE</code> 58 * and <code>BluetoothProfile.EXTRA_STATE</code>, which 59 * are mandatory. 60 * <p>There are also non mandatory feature extras: 61 * {@link #EXTRA_AG_FEATURE_3WAY_CALLING}, 62 * {@link #EXTRA_AG_FEATURE_VOICE_RECOGNITION}, 63 * {@link #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT}, 64 * {@link #EXTRA_AG_FEATURE_REJECT_CALL}, 65 * {@link #EXTRA_AG_FEATURE_ECC}, 66 * {@link #EXTRA_AG_FEATURE_RESPONSE_AND_HOLD}, 67 * {@link #EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL}, 68 * {@link #EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL}, 69 * {@link #EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT}, 70 * {@link #EXTRA_AG_FEATURE_MERGE}, 71 * {@link #EXTRA_AG_FEATURE_MERGE_AND_DETACH}, 72 * sent as boolean values only when <code>EXTRA_STATE</code> 73 * is set to <code>STATE_CONNECTED</code>.</p> 74 * 75 * <p>Note that features supported by AG are being sent as 76 * booleans with value <code>true</code>, 77 * and not supported ones are <strong>not</strong> being sent at all.</p> 78 */ 79 @RequiresBluetoothConnectPermission 80 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 81 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 82 public static final String ACTION_CONNECTION_STATE_CHANGED = 83 "android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED"; 84 85 /** 86 * Intent sent whenever audio state changes. 87 * 88 * <p>It includes two mandatory extras: 89 * {@link BluetoothProfile#EXTRA_STATE}, 90 * {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}, 91 * with possible values: 92 * {@link #STATE_AUDIO_CONNECTING}, 93 * {@link #STATE_AUDIO_CONNECTED}, 94 * {@link #STATE_AUDIO_DISCONNECTED}</p> 95 * <p>When <code>EXTRA_STATE</code> is set 96 * to </code>STATE_AUDIO_CONNECTED</code>, 97 * it also includes {@link #EXTRA_AUDIO_WBS} 98 * indicating wide band speech support.</p> 99 */ 100 @RequiresBluetoothConnectPermission 101 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 102 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 103 public static final String ACTION_AUDIO_STATE_CHANGED = 104 "android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED"; 105 106 /** 107 * Intent sending updates of the Audio Gateway state. 108 * Each extra is being sent only when value it 109 * represents has been changed recently on AG. 110 * <p>It can contain one or more of the following extras: 111 * {@link #EXTRA_NETWORK_STATUS}, 112 * {@link #EXTRA_NETWORK_SIGNAL_STRENGTH}, 113 * {@link #EXTRA_NETWORK_ROAMING}, 114 * {@link #EXTRA_BATTERY_LEVEL}, 115 * {@link #EXTRA_OPERATOR_NAME}, 116 * {@link #EXTRA_VOICE_RECOGNITION}, 117 * {@link #EXTRA_IN_BAND_RING}</p> 118 */ 119 @RequiresBluetoothConnectPermission 120 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 121 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 122 public static final String ACTION_AG_EVENT = 123 "android.bluetooth.headsetclient.profile.action.AG_EVENT"; 124 125 /** 126 * Intent sent whenever state of a call changes. 127 * 128 * <p>It includes: 129 * {@link #EXTRA_CALL}, 130 * with value of {@link BluetoothHeadsetClientCall} instance, 131 * representing actual call state.</p> 132 */ 133 @RequiresBluetoothConnectPermission 134 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 135 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 136 public static final String ACTION_CALL_CHANGED = 137 "android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED"; 138 139 /** 140 * Intent that notifies about the result of the last issued action. 141 * Please note that not every action results in explicit action result code being sent. 142 * Instead other notifications about new Audio Gateway state might be sent, 143 * like <code>ACTION_AG_EVENT</code> with <code>EXTRA_VOICE_RECOGNITION</code> value 144 * when for example user started voice recognition from HF unit. 145 */ 146 @RequiresBluetoothConnectPermission 147 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 148 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 149 public static final String ACTION_RESULT = 150 "android.bluetooth.headsetclient.profile.action.RESULT"; 151 152 /** 153 * Intent that notifies about vendor specific event arrival. Events not defined in 154 * HFP spec will be matched with supported vendor event list and this intent will 155 * be broadcasted upon a match. Supported vendor events are of format of 156 * of "+eventCode" or "+eventCode=xxxx" or "+eventCode:=xxxx". 157 * Vendor event can be a response to an vendor specific command or unsolicited. 158 * 159 */ 160 @RequiresBluetoothConnectPermission 161 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 162 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 163 public static final String ACTION_VENDOR_SPECIFIC_HEADSETCLIENT_EVENT = 164 "android.bluetooth.headsetclient.profile.action.VENDOR_SPECIFIC_EVENT"; 165 166 /** 167 * Intent that notifies about the number attached to the last voice tag 168 * recorded on AG. 169 * 170 * <p>It contains: 171 * {@link #EXTRA_NUMBER}, 172 * with a <code>String</code> value representing phone number.</p> 173 */ 174 @RequiresBluetoothConnectPermission 175 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 176 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 177 public static final String ACTION_LAST_VTAG = 178 "android.bluetooth.headsetclient.profile.action.LAST_VTAG"; 179 180 public static final int STATE_AUDIO_DISCONNECTED = 0; 181 public static final int STATE_AUDIO_CONNECTING = 1; 182 public static final int STATE_AUDIO_CONNECTED = 2; 183 184 /** 185 * Extra with information if connected audio is WBS. 186 * <p>Possible values: <code>true</code>, 187 * <code>false</code>.</p> 188 */ 189 public static final String EXTRA_AUDIO_WBS = 190 "android.bluetooth.headsetclient.extra.AUDIO_WBS"; 191 192 /** 193 * Extra for AG_EVENT indicates network status. 194 * <p>Value: 0 - network unavailable, 195 * 1 - network available </p> 196 */ 197 public static final String EXTRA_NETWORK_STATUS = 198 "android.bluetooth.headsetclient.extra.NETWORK_STATUS"; 199 /** 200 * Extra for AG_EVENT intent indicates network signal strength. 201 * <p>Value: <code>Integer</code> representing signal strength.</p> 202 */ 203 public static final String EXTRA_NETWORK_SIGNAL_STRENGTH = 204 "android.bluetooth.headsetclient.extra.NETWORK_SIGNAL_STRENGTH"; 205 /** 206 * Extra for AG_EVENT intent indicates roaming state. 207 * <p>Value: 0 - no roaming 208 * 1 - active roaming</p> 209 */ 210 public static final String EXTRA_NETWORK_ROAMING = 211 "android.bluetooth.headsetclient.extra.NETWORK_ROAMING"; 212 /** 213 * Extra for AG_EVENT intent indicates the battery level. 214 * <p>Value: <code>Integer</code> representing signal strength.</p> 215 */ 216 public static final String EXTRA_BATTERY_LEVEL = 217 "android.bluetooth.headsetclient.extra.BATTERY_LEVEL"; 218 /** 219 * Extra for AG_EVENT intent indicates operator name. 220 * <p>Value: <code>String</code> representing operator name.</p> 221 */ 222 public static final String EXTRA_OPERATOR_NAME = 223 "android.bluetooth.headsetclient.extra.OPERATOR_NAME"; 224 /** 225 * Extra for AG_EVENT intent indicates voice recognition state. 226 * <p>Value: 227 * 0 - voice recognition stopped, 228 * 1 - voice recognition started.</p> 229 */ 230 public static final String EXTRA_VOICE_RECOGNITION = 231 "android.bluetooth.headsetclient.extra.VOICE_RECOGNITION"; 232 /** 233 * Extra for AG_EVENT intent indicates in band ring state. 234 * <p>Value: 235 * 0 - in band ring tone not supported, or 236 * 1 - in band ring tone supported.</p> 237 */ 238 public static final String EXTRA_IN_BAND_RING = 239 "android.bluetooth.headsetclient.extra.IN_BAND_RING"; 240 241 /** 242 * Extra for AG_EVENT intent indicates subscriber info. 243 * <p>Value: <code>String</code> containing subscriber information.</p> 244 */ 245 public static final String EXTRA_SUBSCRIBER_INFO = 246 "android.bluetooth.headsetclient.extra.SUBSCRIBER_INFO"; 247 248 /** 249 * Extra for AG_CALL_CHANGED intent indicates the 250 * {@link BluetoothHeadsetClientCall} object that has changed. 251 */ 252 public static final String EXTRA_CALL = 253 "android.bluetooth.headsetclient.extra.CALL"; 254 255 /** 256 * Extra for ACTION_LAST_VTAG intent. 257 * <p>Value: <code>String</code> representing phone number 258 * corresponding to last voice tag recorded on AG</p> 259 */ 260 public static final String EXTRA_NUMBER = 261 "android.bluetooth.headsetclient.extra.NUMBER"; 262 263 /** 264 * Extra for ACTION_RESULT intent that shows the result code of 265 * last issued action. 266 * <p>Possible results: 267 * {@link #ACTION_RESULT_OK}, 268 * {@link #ACTION_RESULT_ERROR}, 269 * {@link #ACTION_RESULT_ERROR_NO_CARRIER}, 270 * {@link #ACTION_RESULT_ERROR_BUSY}, 271 * {@link #ACTION_RESULT_ERROR_NO_ANSWER}, 272 * {@link #ACTION_RESULT_ERROR_DELAYED}, 273 * {@link #ACTION_RESULT_ERROR_BLACKLISTED}, 274 * {@link #ACTION_RESULT_ERROR_CME}</p> 275 */ 276 public static final String EXTRA_RESULT_CODE = 277 "android.bluetooth.headsetclient.extra.RESULT_CODE"; 278 279 /** 280 * Extra for ACTION_RESULT intent that shows the extended result code of 281 * last issued action. 282 * <p>Value: <code>Integer</code> - error code.</p> 283 */ 284 public static final String EXTRA_CME_CODE = 285 "android.bluetooth.headsetclient.extra.CME_CODE"; 286 287 /** 288 * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that 289 * indicates vendor ID. 290 */ 291 public static final String EXTRA_VENDOR_ID = 292 "android.bluetooth.headsetclient.extra.VENDOR_ID"; 293 294 /** 295 * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that 296 * indicates vendor event code. 297 */ 298 public static final String EXTRA_VENDOR_EVENT_CODE = 299 "android.bluetooth.headsetclient.extra.VENDOR_EVENT_CODE"; 300 301 /** 302 * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that 303 * contains full vendor event including event code and full arguments. 304 */ 305 public static final String EXTRA_VENDOR_EVENT_FULL_ARGS = 306 "android.bluetooth.headsetclient.extra.VENDOR_EVENT_FULL_ARGS"; 307 308 309 /* Extras for AG_FEATURES, extras type is boolean */ 310 // TODO verify if all of those are actually useful 311 /** 312 * AG feature: three way calling. 313 */ 314 public static final String EXTRA_AG_FEATURE_3WAY_CALLING = 315 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_3WAY_CALLING"; 316 /** 317 * AG feature: voice recognition. 318 */ 319 public static final String EXTRA_AG_FEATURE_VOICE_RECOGNITION = 320 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_VOICE_RECOGNITION"; 321 /** 322 * AG feature: fetching phone number for voice tagging procedure. 323 */ 324 public static final String EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT = 325 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT"; 326 /** 327 * AG feature: ability to reject incoming call. 328 */ 329 public static final String EXTRA_AG_FEATURE_REJECT_CALL = 330 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_REJECT_CALL"; 331 /** 332 * AG feature: enhanced call handling (terminate specific call, private consultation). 333 */ 334 public static final String EXTRA_AG_FEATURE_ECC = 335 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ECC"; 336 /** 337 * AG feature: response and hold. 338 */ 339 public static final String EXTRA_AG_FEATURE_RESPONSE_AND_HOLD = 340 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RESPONSE_AND_HOLD"; 341 /** 342 * AG call handling feature: accept held or waiting call in three way calling scenarios. 343 */ 344 public static final String EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL = 345 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL"; 346 /** 347 * AG call handling feature: release held or waiting call in three way calling scenarios. 348 */ 349 public static final String EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL = 350 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL"; 351 /** 352 * AG call handling feature: release active call and accept held or waiting call in three way 353 * calling scenarios. 354 */ 355 public static final String EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT = 356 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT"; 357 /** 358 * AG call handling feature: merge two calls, held and active - multi party conference mode. 359 */ 360 public static final String EXTRA_AG_FEATURE_MERGE = 361 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE"; 362 /** 363 * AG call handling feature: merge calls and disconnect from multi party 364 * conversation leaving peers connected to each other. 365 * Note that this feature needs to be supported by mobile network operator 366 * as it requires connection and billing transfer. 367 */ 368 public static final String EXTRA_AG_FEATURE_MERGE_AND_DETACH = 369 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE_AND_DETACH"; 370 371 /* Action result codes */ 372 public static final int ACTION_RESULT_OK = 0; 373 public static final int ACTION_RESULT_ERROR = 1; 374 public static final int ACTION_RESULT_ERROR_NO_CARRIER = 2; 375 public static final int ACTION_RESULT_ERROR_BUSY = 3; 376 public static final int ACTION_RESULT_ERROR_NO_ANSWER = 4; 377 public static final int ACTION_RESULT_ERROR_DELAYED = 5; 378 public static final int ACTION_RESULT_ERROR_BLACKLISTED = 6; 379 public static final int ACTION_RESULT_ERROR_CME = 7; 380 381 /* Detailed CME error codes */ 382 public static final int CME_PHONE_FAILURE = 0; 383 public static final int CME_NO_CONNECTION_TO_PHONE = 1; 384 public static final int CME_OPERATION_NOT_ALLOWED = 3; 385 public static final int CME_OPERATION_NOT_SUPPORTED = 4; 386 public static final int CME_PHSIM_PIN_REQUIRED = 5; 387 public static final int CME_PHFSIM_PIN_REQUIRED = 6; 388 public static final int CME_PHFSIM_PUK_REQUIRED = 7; 389 public static final int CME_SIM_NOT_INSERTED = 10; 390 public static final int CME_SIM_PIN_REQUIRED = 11; 391 public static final int CME_SIM_PUK_REQUIRED = 12; 392 public static final int CME_SIM_FAILURE = 13; 393 public static final int CME_SIM_BUSY = 14; 394 public static final int CME_SIM_WRONG = 15; 395 public static final int CME_INCORRECT_PASSWORD = 16; 396 public static final int CME_SIM_PIN2_REQUIRED = 17; 397 public static final int CME_SIM_PUK2_REQUIRED = 18; 398 public static final int CME_MEMORY_FULL = 20; 399 public static final int CME_INVALID_INDEX = 21; 400 public static final int CME_NOT_FOUND = 22; 401 public static final int CME_MEMORY_FAILURE = 23; 402 public static final int CME_TEXT_STRING_TOO_LONG = 24; 403 public static final int CME_INVALID_CHARACTER_IN_TEXT_STRING = 25; 404 public static final int CME_DIAL_STRING_TOO_LONG = 26; 405 public static final int CME_INVALID_CHARACTER_IN_DIAL_STRING = 27; 406 public static final int CME_NO_NETWORK_SERVICE = 30; 407 public static final int CME_NETWORK_TIMEOUT = 31; 408 public static final int CME_EMERGENCY_SERVICE_ONLY = 32; 409 public static final int CME_NO_SIMULTANOUS_VOIP_CS_CALLS = 33; 410 public static final int CME_NOT_SUPPORTED_FOR_VOIP = 34; 411 public static final int CME_SIP_RESPONSE_CODE = 35; 412 public static final int CME_NETWORK_PERSONALIZATION_PIN_REQUIRED = 40; 413 public static final int CME_NETWORK_PERSONALIZATION_PUK_REQUIRED = 41; 414 public static final int CME_NETWORK_SUBSET_PERSONALIZATION_PIN_REQUIRED = 42; 415 public static final int CME_NETWORK_SUBSET_PERSONALIZATION_PUK_REQUIRED = 43; 416 public static final int CME_SERVICE_PROVIDER_PERSONALIZATION_PIN_REQUIRED = 44; 417 public static final int CME_SERVICE_PROVIDER_PERSONALIZATION_PUK_REQUIRED = 45; 418 public static final int CME_CORPORATE_PERSONALIZATION_PIN_REQUIRED = 46; 419 public static final int CME_CORPORATE_PERSONALIZATION_PUK_REQUIRED = 47; 420 public static final int CME_HIDDEN_KEY_REQUIRED = 48; 421 public static final int CME_EAP_NOT_SUPPORTED = 49; 422 public static final int CME_INCORRECT_PARAMETERS = 50; 423 424 /* Action policy for other calls when accepting call */ 425 public static final int CALL_ACCEPT_NONE = 0; 426 public static final int CALL_ACCEPT_HOLD = 1; 427 public static final int CALL_ACCEPT_TERMINATE = 2; 428 429 private final BluetoothAdapter mAdapter; 430 private final AttributionSource mAttributionSource; 431 private final BluetoothProfileConnector<IBluetoothHeadsetClient> mProfileConnector = 432 new BluetoothProfileConnector(this, BluetoothProfile.HEADSET_CLIENT, 433 "BluetoothHeadsetClient", IBluetoothHeadsetClient.class.getName()) { 434 @Override 435 public IBluetoothHeadsetClient getServiceInterface(IBinder service) { 436 return IBluetoothHeadsetClient.Stub.asInterface(Binder.allowBlocking(service)); 437 } 438 }; 439 440 /** 441 * Create a BluetoothHeadsetClient proxy object. 442 */ BluetoothHeadsetClient(Context context, ServiceListener listener, BluetoothAdapter adapter)443 /* package */ BluetoothHeadsetClient(Context context, ServiceListener listener, 444 BluetoothAdapter adapter) { 445 mAdapter = adapter; 446 mAttributionSource = adapter.getAttributionSource(); 447 mProfileConnector.connect(context, listener); 448 } 449 450 /** 451 * Close the connection to the backing service. 452 * Other public functions of BluetoothHeadsetClient will return default error 453 * results once close() has been called. Multiple invocations of close() 454 * are ok. 455 */ close()456 /*package*/ void close() { 457 if (VDBG) log("close()"); 458 mProfileConnector.disconnect(); 459 } 460 getService()461 private IBluetoothHeadsetClient getService() { 462 return mProfileConnector.getService(); 463 } 464 465 /** 466 * Connects to remote device. 467 * 468 * Currently, the system supports only 1 connection. So, in case of the 469 * second connection, this implementation will disconnect already connected 470 * device automatically and will process the new one. 471 * 472 * @param device a remote device we want connect to 473 * @return <code>true</code> if command has been issued successfully; <code>false</code> 474 * otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent. 475 * 476 * @hide 477 */ 478 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 479 @RequiresBluetoothConnectPermission 480 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) connect(BluetoothDevice device)481 public boolean connect(BluetoothDevice device) { 482 if (DBG) log("connect(" + device + ")"); 483 final IBluetoothHeadsetClient service = 484 getService(); 485 if (service != null && isEnabled() && isValidDevice(device)) { 486 try { 487 return service.connect(device, mAttributionSource); 488 } catch (RemoteException e) { 489 Log.e(TAG, Log.getStackTraceString(new Throwable())); 490 return false; 491 } 492 } 493 if (service == null) Log.w(TAG, "Proxy not attached to service"); 494 return false; 495 } 496 497 /** 498 * Disconnects remote device 499 * 500 * @param device a remote device we want disconnect 501 * @return <code>true</code> if command has been issued successfully; <code>false</code> 502 * otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent. 503 * 504 * @hide 505 */ 506 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 507 @RequiresBluetoothConnectPermission 508 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) disconnect(BluetoothDevice device)509 public boolean disconnect(BluetoothDevice device) { 510 if (DBG) log("disconnect(" + device + ")"); 511 final IBluetoothHeadsetClient service = 512 getService(); 513 if (service != null && isEnabled() && isValidDevice(device)) { 514 try { 515 return service.disconnect(device, mAttributionSource); 516 } catch (RemoteException e) { 517 Log.e(TAG, Log.getStackTraceString(new Throwable())); 518 return false; 519 } 520 } 521 if (service == null) Log.w(TAG, "Proxy not attached to service"); 522 return false; 523 } 524 525 /** 526 * Return the list of connected remote devices 527 * 528 * @return list of connected devices; empty list if nothing is connected. 529 */ 530 @Override 531 @RequiresBluetoothConnectPermission 532 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getConnectedDevices()533 public List<BluetoothDevice> getConnectedDevices() { 534 if (VDBG) log("getConnectedDevices()"); 535 final IBluetoothHeadsetClient service = 536 getService(); 537 if (service != null && isEnabled()) { 538 try { 539 return Attributable.setAttributionSource( 540 service.getConnectedDevices(mAttributionSource), mAttributionSource); 541 } catch (RemoteException e) { 542 Log.e(TAG, Log.getStackTraceString(new Throwable())); 543 return new ArrayList<BluetoothDevice>(); 544 } 545 } 546 if (service == null) Log.w(TAG, "Proxy not attached to service"); 547 return new ArrayList<BluetoothDevice>(); 548 } 549 550 /** 551 * Returns list of remote devices in a particular state 552 * 553 * @param states collection of states 554 * @return list of devices that state matches the states listed in <code>states</code>; empty 555 * list if nothing matches the <code>states</code> 556 */ 557 @Override 558 @RequiresBluetoothConnectPermission 559 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getDevicesMatchingConnectionStates(int[] states)560 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 561 if (VDBG) log("getDevicesMatchingStates()"); 562 final IBluetoothHeadsetClient service = 563 getService(); 564 if (service != null && isEnabled()) { 565 try { 566 return Attributable.setAttributionSource( 567 service.getDevicesMatchingConnectionStates(states, mAttributionSource), 568 mAttributionSource); 569 } catch (RemoteException e) { 570 Log.e(TAG, Log.getStackTraceString(new Throwable())); 571 return new ArrayList<BluetoothDevice>(); 572 } 573 } 574 if (service == null) Log.w(TAG, "Proxy not attached to service"); 575 return new ArrayList<BluetoothDevice>(); 576 } 577 578 /** 579 * Returns state of the <code>device</code> 580 * 581 * @param device a remote device 582 * @return the state of connection of the device 583 */ 584 @Override 585 @RequiresBluetoothConnectPermission 586 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getConnectionState(BluetoothDevice device)587 public int getConnectionState(BluetoothDevice device) { 588 if (VDBG) log("getConnectionState(" + device + ")"); 589 final IBluetoothHeadsetClient service = 590 getService(); 591 if (service != null && isEnabled() && isValidDevice(device)) { 592 try { 593 return service.getConnectionState(device, mAttributionSource); 594 } catch (RemoteException e) { 595 Log.e(TAG, Log.getStackTraceString(new Throwable())); 596 return BluetoothProfile.STATE_DISCONNECTED; 597 } 598 } 599 if (service == null) Log.w(TAG, "Proxy not attached to service"); 600 return BluetoothProfile.STATE_DISCONNECTED; 601 } 602 603 /** 604 * Set priority of the profile 605 * 606 * <p> The device should already be paired. 607 * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF} 608 * 609 * @param device Paired bluetooth device 610 * @param priority 611 * @return true if priority is set, false on error 612 * @hide 613 */ 614 @RequiresBluetoothConnectPermission 615 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) setPriority(BluetoothDevice device, int priority)616 public boolean setPriority(BluetoothDevice device, int priority) { 617 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 618 return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); 619 } 620 621 /** 622 * Set connection policy of the profile 623 * 624 * <p> The device should already be paired. 625 * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, 626 * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} 627 * 628 * @param device Paired bluetooth device 629 * @param connectionPolicy is the connection policy to set to for this profile 630 * @return true if connectionPolicy is set, false on error 631 * @hide 632 */ 633 @RequiresBluetoothConnectPermission 634 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) setConnectionPolicy(@onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)635 public boolean setConnectionPolicy(@NonNull BluetoothDevice device, 636 @ConnectionPolicy int connectionPolicy) { 637 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); 638 final IBluetoothHeadsetClient service = 639 getService(); 640 if (service != null && isEnabled() && isValidDevice(device)) { 641 if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 642 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 643 return false; 644 } 645 try { 646 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); 647 } catch (RemoteException e) { 648 Log.e(TAG, Log.getStackTraceString(new Throwable())); 649 return false; 650 } 651 } 652 if (service == null) Log.w(TAG, "Proxy not attached to service"); 653 return false; 654 } 655 656 /** 657 * Get the priority of the profile. 658 * 659 * <p> The priority can be any of: 660 * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} 661 * 662 * @param device Bluetooth device 663 * @return priority of the device 664 * @hide 665 */ 666 @RequiresLegacyBluetoothPermission 667 @RequiresBluetoothConnectPermission 668 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getPriority(BluetoothDevice device)669 public int getPriority(BluetoothDevice device) { 670 if (VDBG) log("getPriority(" + device + ")"); 671 return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); 672 } 673 674 /** 675 * Get the connection policy of the profile. 676 * 677 * <p> The connection policy can be any of: 678 * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, 679 * {@link #CONNECTION_POLICY_UNKNOWN} 680 * 681 * @param device Bluetooth device 682 * @return connection policy of the device 683 * @hide 684 */ 685 @RequiresLegacyBluetoothPermission 686 @RequiresBluetoothConnectPermission 687 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getConnectionPolicy(@onNull BluetoothDevice device)688 public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { 689 if (VDBG) log("getConnectionPolicy(" + device + ")"); 690 final IBluetoothHeadsetClient service = 691 getService(); 692 if (service != null && isEnabled() && isValidDevice(device)) { 693 try { 694 return service.getConnectionPolicy(device, mAttributionSource); 695 } catch (RemoteException e) { 696 Log.e(TAG, Log.getStackTraceString(new Throwable())); 697 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 698 } 699 } 700 if (service == null) Log.w(TAG, "Proxy not attached to service"); 701 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 702 } 703 704 /** 705 * Starts voice recognition. 706 * 707 * @param device remote device 708 * @return <code>true</code> if command has been issued successfully; <code>false</code> 709 * otherwise; upon completion HFP sends {@link #ACTION_AG_EVENT} intent. 710 * 711 * <p>Feature required for successful execution is being reported by: {@link 712 * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature 713 * is not supported.</p> 714 */ 715 @RequiresBluetoothConnectPermission 716 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) startVoiceRecognition(BluetoothDevice device)717 public boolean startVoiceRecognition(BluetoothDevice device) { 718 if (DBG) log("startVoiceRecognition()"); 719 final IBluetoothHeadsetClient service = 720 getService(); 721 if (service != null && isEnabled() && isValidDevice(device)) { 722 try { 723 return service.startVoiceRecognition(device, mAttributionSource); 724 } catch (RemoteException e) { 725 Log.e(TAG, Log.getStackTraceString(new Throwable())); 726 } 727 } 728 if (service == null) Log.w(TAG, "Proxy not attached to service"); 729 return false; 730 } 731 732 /** 733 * Send vendor specific AT command. 734 * 735 * @param device remote device 736 * @param vendorId vendor number by Bluetooth SIG 737 * @param atCommand command to be sent. It start with + prefix and only one command at one time. 738 * @return <code>true</code> if command has been issued successfully; <code>false</code> 739 * otherwise. 740 */ 741 @RequiresBluetoothConnectPermission 742 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand)743 public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId, 744 String atCommand) { 745 if (DBG) log("sendVendorSpecificCommand()"); 746 final IBluetoothHeadsetClient service = 747 getService(); 748 if (service != null && isEnabled() && isValidDevice(device)) { 749 try { 750 return service.sendVendorAtCommand(device, vendorId, atCommand, mAttributionSource); 751 } catch (RemoteException e) { 752 Log.e(TAG, Log.getStackTraceString(new Throwable())); 753 } 754 } 755 if (service == null) Log.w(TAG, "Proxy not attached to service"); 756 return false; 757 } 758 759 /** 760 * Stops voice recognition. 761 * 762 * @param device remote device 763 * @return <code>true</code> if command has been issued successfully; <code>false</code> 764 * otherwise; upon completion HFP sends {@link #ACTION_AG_EVENT} intent. 765 * 766 * <p>Feature required for successful execution is being reported by: {@link 767 * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature 768 * is not supported.</p> 769 */ 770 @RequiresBluetoothConnectPermission 771 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) stopVoiceRecognition(BluetoothDevice device)772 public boolean stopVoiceRecognition(BluetoothDevice device) { 773 if (DBG) log("stopVoiceRecognition()"); 774 final IBluetoothHeadsetClient service = 775 getService(); 776 if (service != null && isEnabled() && isValidDevice(device)) { 777 try { 778 return service.stopVoiceRecognition(device, mAttributionSource); 779 } catch (RemoteException e) { 780 Log.e(TAG, Log.getStackTraceString(new Throwable())); 781 } 782 } 783 if (service == null) Log.w(TAG, "Proxy not attached to service"); 784 return false; 785 } 786 787 /** 788 * Returns list of all calls in any state. 789 * 790 * @param device remote device 791 * @return list of calls; empty list if none call exists 792 */ 793 @RequiresBluetoothConnectPermission 794 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getCurrentCalls(BluetoothDevice device)795 public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) { 796 if (DBG) log("getCurrentCalls()"); 797 final IBluetoothHeadsetClient service = 798 getService(); 799 if (service != null && isEnabled() && isValidDevice(device)) { 800 try { 801 return Attributable.setAttributionSource( 802 service.getCurrentCalls(device, mAttributionSource), mAttributionSource); 803 } catch (RemoteException e) { 804 Log.e(TAG, Log.getStackTraceString(new Throwable())); 805 } 806 } 807 if (service == null) Log.w(TAG, "Proxy not attached to service"); 808 return null; 809 } 810 811 /** 812 * Returns list of current values of AG indicators. 813 * 814 * @param device remote device 815 * @return bundle of AG indicators; null if device is not in CONNECTED state 816 */ 817 @RequiresBluetoothConnectPermission 818 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getCurrentAgEvents(BluetoothDevice device)819 public Bundle getCurrentAgEvents(BluetoothDevice device) { 820 if (DBG) log("getCurrentCalls()"); 821 final IBluetoothHeadsetClient service = 822 getService(); 823 if (service != null && isEnabled() && isValidDevice(device)) { 824 try { 825 return service.getCurrentAgEvents(device, mAttributionSource); 826 } catch (RemoteException e) { 827 Log.e(TAG, Log.getStackTraceString(new Throwable())); 828 } 829 } 830 if (service == null) Log.w(TAG, "Proxy not attached to service"); 831 return null; 832 } 833 834 /** 835 * Accepts a call 836 * 837 * @param device remote device 838 * @param flag action policy while accepting a call. Possible values {@link #CALL_ACCEPT_NONE}, 839 * {@link #CALL_ACCEPT_HOLD}, {@link #CALL_ACCEPT_TERMINATE} 840 * @return <code>true</code> if command has been issued successfully; <code>false</code> 841 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. 842 */ 843 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 844 @RequiresBluetoothConnectPermission 845 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) acceptCall(BluetoothDevice device, int flag)846 public boolean acceptCall(BluetoothDevice device, int flag) { 847 if (DBG) log("acceptCall()"); 848 final IBluetoothHeadsetClient service = 849 getService(); 850 if (service != null && isEnabled() && isValidDevice(device)) { 851 try { 852 return service.acceptCall(device, flag, mAttributionSource); 853 } catch (RemoteException e) { 854 Log.e(TAG, Log.getStackTraceString(new Throwable())); 855 } 856 } 857 if (service == null) Log.w(TAG, "Proxy not attached to service"); 858 return false; 859 } 860 861 /** 862 * Holds a call. 863 * 864 * @param device remote device 865 * @return <code>true</code> if command has been issued successfully; <code>false</code> 866 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. 867 */ 868 @RequiresBluetoothConnectPermission 869 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) holdCall(BluetoothDevice device)870 public boolean holdCall(BluetoothDevice device) { 871 if (DBG) log("holdCall()"); 872 final IBluetoothHeadsetClient service = 873 getService(); 874 if (service != null && isEnabled() && isValidDevice(device)) { 875 try { 876 return service.holdCall(device, mAttributionSource); 877 } catch (RemoteException e) { 878 Log.e(TAG, Log.getStackTraceString(new Throwable())); 879 } 880 } 881 if (service == null) Log.w(TAG, "Proxy not attached to service"); 882 return false; 883 } 884 885 /** 886 * Rejects a call. 887 * 888 * @param device remote device 889 * @return <code>true</code> if command has been issued successfully; <code>false</code> 890 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. 891 * 892 * <p>Feature required for successful execution is being reported by: {@link 893 * #EXTRA_AG_FEATURE_REJECT_CALL}. This method invocation will fail silently when feature is not 894 * supported.</p> 895 */ 896 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 897 @RequiresBluetoothConnectPermission 898 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) rejectCall(BluetoothDevice device)899 public boolean rejectCall(BluetoothDevice device) { 900 if (DBG) log("rejectCall()"); 901 final IBluetoothHeadsetClient service = 902 getService(); 903 if (service != null && isEnabled() && isValidDevice(device)) { 904 try { 905 return service.rejectCall(device, mAttributionSource); 906 } catch (RemoteException e) { 907 Log.e(TAG, Log.getStackTraceString(new Throwable())); 908 } 909 } 910 if (service == null) Log.w(TAG, "Proxy not attached to service"); 911 return false; 912 } 913 914 /** 915 * Terminates a specified call. 916 * 917 * Works only when Extended Call Control is supported by Audio Gateway. 918 * 919 * @param device remote device 920 * @param call Handle of call obtained in {@link #dial(BluetoothDevice, String)} or obtained via 921 * {@link #ACTION_CALL_CHANGED}. {@code call} may be null in which case we will hangup all active 922 * calls. 923 * @return <code>true</code> if command has been issued successfully; <code>false</code> 924 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. 925 * 926 * <p>Feature required for successful execution is being reported by: {@link 927 * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not 928 * supported.</p> 929 */ 930 @RequiresBluetoothConnectPermission 931 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call)932 public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call) { 933 if (DBG) log("terminateCall()"); 934 final IBluetoothHeadsetClient service = 935 getService(); 936 if (service != null && isEnabled() && isValidDevice(device)) { 937 try { 938 return service.terminateCall(device, call, mAttributionSource); 939 } catch (RemoteException e) { 940 Log.e(TAG, Log.getStackTraceString(new Throwable())); 941 } 942 } 943 if (service == null) Log.w(TAG, "Proxy not attached to service"); 944 return false; 945 } 946 947 /** 948 * Enters private mode with a specified call. 949 * 950 * Works only when Extended Call Control is supported by Audio Gateway. 951 * 952 * @param device remote device 953 * @param index index of the call to connect in private mode 954 * @return <code>true</code> if command has been issued successfully; <code>false</code> 955 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. 956 * 957 * <p>Feature required for successful execution is being reported by: {@link 958 * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not 959 * supported.</p> 960 */ 961 @RequiresBluetoothConnectPermission 962 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) enterPrivateMode(BluetoothDevice device, int index)963 public boolean enterPrivateMode(BluetoothDevice device, int index) { 964 if (DBG) log("enterPrivateMode()"); 965 final IBluetoothHeadsetClient service = 966 getService(); 967 if (service != null && isEnabled() && isValidDevice(device)) { 968 try { 969 return service.enterPrivateMode(device, index, mAttributionSource); 970 } catch (RemoteException e) { 971 Log.e(TAG, Log.getStackTraceString(new Throwable())); 972 } 973 } 974 if (service == null) Log.w(TAG, "Proxy not attached to service"); 975 return false; 976 } 977 978 /** 979 * Performs explicit call transfer. 980 * 981 * That means connect other calls and disconnect. 982 * 983 * @param device remote device 984 * @return <code>true</code> if command has been issued successfully; <code>false</code> 985 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. 986 * 987 * <p>Feature required for successful execution is being reported by: {@link 988 * #EXTRA_AG_FEATURE_MERGE_AND_DETACH}. This method invocation will fail silently when feature 989 * is not supported.</p> 990 */ 991 @RequiresBluetoothConnectPermission 992 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) explicitCallTransfer(BluetoothDevice device)993 public boolean explicitCallTransfer(BluetoothDevice device) { 994 if (DBG) log("explicitCallTransfer()"); 995 final IBluetoothHeadsetClient service = 996 getService(); 997 if (service != null && isEnabled() && isValidDevice(device)) { 998 try { 999 return service.explicitCallTransfer(device, mAttributionSource); 1000 } catch (RemoteException e) { 1001 Log.e(TAG, Log.getStackTraceString(new Throwable())); 1002 } 1003 } 1004 if (service == null) Log.w(TAG, "Proxy not attached to service"); 1005 return false; 1006 } 1007 1008 /** 1009 * Places a call with specified number. 1010 * 1011 * @param device remote device 1012 * @param number valid phone number 1013 * @return <code>{@link BluetoothHeadsetClientCall} call</code> if command has been issued 1014 * successfully; <code>{@link null}</code> otherwise; upon completion HFP sends {@link 1015 * #ACTION_CALL_CHANGED} intent in case of success; {@link #ACTION_RESULT} is sent otherwise; 1016 */ 1017 @RequiresBluetoothConnectPermission 1018 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) dial(BluetoothDevice device, String number)1019 public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) { 1020 if (DBG) log("dial()"); 1021 final IBluetoothHeadsetClient service = 1022 getService(); 1023 if (service != null && isEnabled() && isValidDevice(device)) { 1024 try { 1025 return Attributable.setAttributionSource( 1026 service.dial(device, number, mAttributionSource), mAttributionSource); 1027 } catch (RemoteException e) { 1028 Log.e(TAG, Log.getStackTraceString(new Throwable())); 1029 } 1030 } 1031 if (service == null) Log.w(TAG, "Proxy not attached to service"); 1032 return null; 1033 } 1034 1035 /** 1036 * Sends DTMF code. 1037 * 1038 * Possible code values : 0,1,2,3,4,5,6,7,8,9,A,B,C,D,*,# 1039 * 1040 * @param device remote device 1041 * @param code ASCII code 1042 * @return <code>true</code> if command has been issued successfully; <code>false</code> 1043 * otherwise; upon completion HFP sends {@link #ACTION_RESULT} intent; 1044 */ 1045 @RequiresBluetoothConnectPermission 1046 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) sendDTMF(BluetoothDevice device, byte code)1047 public boolean sendDTMF(BluetoothDevice device, byte code) { 1048 if (DBG) log("sendDTMF()"); 1049 final IBluetoothHeadsetClient service = 1050 getService(); 1051 if (service != null && isEnabled() && isValidDevice(device)) { 1052 try { 1053 return service.sendDTMF(device, code, mAttributionSource); 1054 } catch (RemoteException e) { 1055 Log.e(TAG, Log.getStackTraceString(new Throwable())); 1056 } 1057 } 1058 if (service == null) Log.w(TAG, "Proxy not attached to service"); 1059 return false; 1060 } 1061 1062 /** 1063 * Get a number corresponding to last voice tag recorded on AG. 1064 * 1065 * @param device remote device 1066 * @return <code>true</code> if command has been issued successfully; <code>false</code> 1067 * otherwise; upon completion HFP sends {@link #ACTION_LAST_VTAG} or {@link #ACTION_RESULT} 1068 * intent; 1069 * 1070 * <p>Feature required for successful execution is being reported by: {@link 1071 * #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT}. This method invocation will fail silently when 1072 * feature is not supported.</p> 1073 */ 1074 @RequiresBluetoothConnectPermission 1075 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getLastVoiceTagNumber(BluetoothDevice device)1076 public boolean getLastVoiceTagNumber(BluetoothDevice device) { 1077 if (DBG) log("getLastVoiceTagNumber()"); 1078 final IBluetoothHeadsetClient service = 1079 getService(); 1080 if (service != null && isEnabled() && isValidDevice(device)) { 1081 try { 1082 return service.getLastVoiceTagNumber(device, mAttributionSource); 1083 } catch (RemoteException e) { 1084 Log.e(TAG, Log.getStackTraceString(new Throwable())); 1085 } 1086 } 1087 if (service == null) Log.w(TAG, "Proxy not attached to service"); 1088 return false; 1089 } 1090 1091 /** 1092 * Returns current audio state of Audio Gateway. 1093 * 1094 * Note: This is an internal function and shouldn't be exposed 1095 */ 1096 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 1097 @RequiresBluetoothConnectPermission 1098 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getAudioState(BluetoothDevice device)1099 public int getAudioState(BluetoothDevice device) { 1100 if (VDBG) log("getAudioState"); 1101 final IBluetoothHeadsetClient service = 1102 getService(); 1103 if (service != null && isEnabled()) { 1104 try { 1105 return service.getAudioState(device, mAttributionSource); 1106 } catch (RemoteException e) { 1107 Log.e(TAG, e.toString()); 1108 } 1109 } else { 1110 Log.w(TAG, "Proxy not attached to service"); 1111 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 1112 } 1113 return BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED; 1114 } 1115 1116 /** 1117 * Sets whether audio routing is allowed. 1118 * 1119 * @param device remote device 1120 * @param allowed if routing is allowed to the device Note: This is an internal function and 1121 * shouldn't be exposed 1122 */ 1123 @RequiresBluetoothConnectPermission 1124 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) setAudioRouteAllowed(BluetoothDevice device, boolean allowed)1125 public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) { 1126 if (VDBG) log("setAudioRouteAllowed"); 1127 final IBluetoothHeadsetClient service = 1128 getService(); 1129 if (service != null && isEnabled()) { 1130 try { 1131 service.setAudioRouteAllowed(device, allowed, mAttributionSource); 1132 } catch (RemoteException e) { 1133 Log.e(TAG, e.toString()); 1134 } 1135 } else { 1136 Log.w(TAG, "Proxy not attached to service"); 1137 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 1138 } 1139 } 1140 1141 /** 1142 * Returns whether audio routing is allowed. 1143 * 1144 * @param device remote device 1145 * @return whether the command succeeded Note: This is an internal function and shouldn't be 1146 * exposed 1147 */ 1148 @RequiresBluetoothConnectPermission 1149 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getAudioRouteAllowed(BluetoothDevice device)1150 public boolean getAudioRouteAllowed(BluetoothDevice device) { 1151 if (VDBG) log("getAudioRouteAllowed"); 1152 final IBluetoothHeadsetClient service = 1153 getService(); 1154 if (service != null && isEnabled()) { 1155 try { 1156 return service.getAudioRouteAllowed(device, mAttributionSource); 1157 } catch (RemoteException e) { 1158 Log.e(TAG, e.toString()); 1159 } 1160 } else { 1161 Log.w(TAG, "Proxy not attached to service"); 1162 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 1163 } 1164 return false; 1165 } 1166 1167 /** 1168 * Initiates a connection of audio channel. 1169 * 1170 * It setup SCO channel with remote connected Handsfree AG device. 1171 * 1172 * @param device remote device 1173 * @return <code>true</code> if command has been issued successfully; <code>false</code> 1174 * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent; 1175 */ 1176 @RequiresBluetoothConnectPermission 1177 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) connectAudio(BluetoothDevice device)1178 public boolean connectAudio(BluetoothDevice device) { 1179 final IBluetoothHeadsetClient service = 1180 getService(); 1181 if (service != null && isEnabled()) { 1182 try { 1183 return service.connectAudio(device, mAttributionSource); 1184 } catch (RemoteException e) { 1185 Log.e(TAG, e.toString()); 1186 } 1187 } else { 1188 Log.w(TAG, "Proxy not attached to service"); 1189 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 1190 } 1191 return false; 1192 } 1193 1194 /** 1195 * Disconnects audio channel. 1196 * 1197 * It tears down the SCO channel from remote AG device. 1198 * 1199 * @param device remote device 1200 * @return <code>true</code> if command has been issued successfully; <code>false</code> 1201 * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent; 1202 */ 1203 @RequiresBluetoothConnectPermission 1204 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) disconnectAudio(BluetoothDevice device)1205 public boolean disconnectAudio(BluetoothDevice device) { 1206 final IBluetoothHeadsetClient service = 1207 getService(); 1208 if (service != null && isEnabled()) { 1209 try { 1210 return service.disconnectAudio(device, mAttributionSource); 1211 } catch (RemoteException e) { 1212 Log.e(TAG, e.toString()); 1213 } 1214 } else { 1215 Log.w(TAG, "Proxy not attached to service"); 1216 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 1217 } 1218 return false; 1219 } 1220 1221 /** 1222 * Get Audio Gateway features 1223 * 1224 * @param device remote device 1225 * @return bundle of AG features; null if no service or AG not connected 1226 */ 1227 @RequiresBluetoothConnectPermission 1228 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getCurrentAgFeatures(BluetoothDevice device)1229 public Bundle getCurrentAgFeatures(BluetoothDevice device) { 1230 final IBluetoothHeadsetClient service = 1231 getService(); 1232 if (service != null && isEnabled()) { 1233 try { 1234 return service.getCurrentAgFeatures(device, mAttributionSource); 1235 } catch (RemoteException e) { 1236 Log.e(TAG, e.toString()); 1237 } 1238 } else { 1239 Log.w(TAG, "Proxy not attached to service"); 1240 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 1241 } 1242 return null; 1243 } 1244 isEnabled()1245 private boolean isEnabled() { 1246 return mAdapter.getState() == BluetoothAdapter.STATE_ON; 1247 } 1248 isValidDevice(BluetoothDevice device)1249 private static boolean isValidDevice(BluetoothDevice device) { 1250 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); 1251 } 1252 log(String msg)1253 private static void log(String msg) { 1254 Log.d(TAG, msg); 1255 } 1256 } 1257