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