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 17package android.bluetooth; 18 19import android.content.ComponentName; 20import android.content.Context; 21import android.content.Intent; 22import android.content.ServiceConnection; 23import android.os.Binder; 24import android.os.Bundle; 25import android.os.IBinder; 26import android.os.RemoteException; 27import android.util.Log; 28 29import java.util.ArrayList; 30import 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 */ 41public 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 static final 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 static final 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 static final 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 static final 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 static final String EXTRA_AG_FEATURE_ECC = 274 "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ECC"; 275 /** 276 * AG feature: response and hold. 277 */ 278 public static final 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 static final 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 static final 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 static final 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 static final 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 static final 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 static final int ACTION_RESULT_OK = 0; 312 public static final int ACTION_RESULT_ERROR = 1; 313 public static final int ACTION_RESULT_ERROR_NO_CARRIER = 2; 314 public static final int ACTION_RESULT_ERROR_BUSY = 3; 315 public static final int ACTION_RESULT_ERROR_NO_ANSWER = 4; 316 public static final int ACTION_RESULT_ERROR_DELAYED = 5; 317 public static final int ACTION_RESULT_ERROR_BLACKLISTED = 6; 318 public static final int ACTION_RESULT_ERROR_CME = 7; 319 320 /* Detailed CME error codes */ 321 public static final int CME_PHONE_FAILURE = 0; 322 public static final int CME_NO_CONNECTION_TO_PHONE = 1; 323 public static final int CME_OPERATION_NOT_ALLOWED = 3; 324 public static final int CME_OPERATION_NOT_SUPPORTED = 4; 325 public static final int CME_PHSIM_PIN_REQUIRED = 5; 326 public static final int CME_PHFSIM_PIN_REQUIRED = 6; 327 public static final int CME_PHFSIM_PUK_REQUIRED = 7; 328 public static final int CME_SIM_NOT_INSERTED = 10; 329 public static final int CME_SIM_PIN_REQUIRED = 11; 330 public static final int CME_SIM_PUK_REQUIRED = 12; 331 public static final int CME_SIM_FAILURE = 13; 332 public static final int CME_SIM_BUSY = 14; 333 public static final int CME_SIM_WRONG = 15; 334 public static final int CME_INCORRECT_PASSWORD = 16; 335 public static final int CME_SIM_PIN2_REQUIRED = 17; 336 public static final int CME_SIM_PUK2_REQUIRED = 18; 337 public static final int CME_MEMORY_FULL = 20; 338 public static final int CME_INVALID_INDEX = 21; 339 public static final int CME_NOT_FOUND = 22; 340 public static final int CME_MEMORY_FAILURE = 23; 341 public static final int CME_TEXT_STRING_TOO_LONG = 24; 342 public static final int CME_INVALID_CHARACTER_IN_TEXT_STRING = 25; 343 public static final int CME_DIAL_STRING_TOO_LONG = 26; 344 public static final int CME_INVALID_CHARACTER_IN_DIAL_STRING = 27; 345 public static final int CME_NO_NETWORK_SERVICE = 30; 346 public static final int CME_NETWORK_TIMEOUT = 31; 347 public static final int CME_EMERGENCY_SERVICE_ONLY = 32; 348 public static final int CME_NO_SIMULTANOUS_VOIP_CS_CALLS = 33; 349 public static final int CME_NOT_SUPPORTED_FOR_VOIP = 34; 350 public static final int CME_SIP_RESPONSE_CODE = 35; 351 public static final int CME_NETWORK_PERSONALIZATION_PIN_REQUIRED = 40; 352 public static final int CME_NETWORK_PERSONALIZATION_PUK_REQUIRED = 41; 353 public static final int CME_NETWORK_SUBSET_PERSONALIZATION_PIN_REQUIRED = 42; 354 public static final int CME_NETWORK_SUBSET_PERSONALIZATION_PUK_REQUIRED = 43; 355 public static final int CME_SERVICE_PROVIDER_PERSONALIZATION_PIN_REQUIRED = 44; 356 public static final int CME_SERVICE_PROVIDER_PERSONALIZATION_PUK_REQUIRED = 45; 357 public static final int CME_CORPORATE_PERSONALIZATION_PIN_REQUIRED = 46; 358 public static final int CME_CORPORATE_PERSONALIZATION_PUK_REQUIRED = 47; 359 public static final int CME_HIDDEN_KEY_REQUIRED = 48; 360 public static final int CME_EAP_NOT_SUPPORTED = 49; 361 public static final 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 private final 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( 394 IBluetoothHeadsetClient.class.getName()); 395 doBind(); 396 } 397 } catch (Exception re) { 398 Log.e(TAG, "", re); 399 } 400 } 401 } 402 } 403 }; 404 405 /** 406 * Create a BluetoothHeadsetClient proxy object. 407 */ 408 /*package*/ BluetoothHeadsetClient(Context context, ServiceListener l) { 409 mContext = context; 410 mServiceListener = l; 411 mAdapter = BluetoothAdapter.getDefaultAdapter(); 412 413 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 414 if (mgr != null) { 415 try { 416 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); 417 } catch (RemoteException e) { 418 Log.e(TAG, "", e); 419 } 420 } 421 422 doBind(); 423 } 424 425 boolean doBind() { 426 Intent intent = new Intent(IBluetoothHeadsetClient.class.getName()); 427 ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); 428 intent.setComponent(comp); 429 if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, 430 mContext.getUser())) { 431 Log.e(TAG, "Could not bind to Bluetooth Headset Client Service with " + intent); 432 return false; 433 } 434 return true; 435 } 436 437 /** 438 * Close the connection to the backing service. 439 * Other public functions of BluetoothHeadsetClient will return default error 440 * results once close() has been called. Multiple invocations of close() 441 * are ok. 442 */ 443 /*package*/ void close() { 444 if (VDBG) log("close()"); 445 446 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 447 if (mgr != null) { 448 try { 449 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); 450 } catch (Exception e) { 451 Log.e(TAG, "", e); 452 } 453 } 454 455 synchronized (mConnection) { 456 if (mService != null) { 457 try { 458 mService = null; 459 mContext.unbindService(mConnection); 460 } catch (Exception re) { 461 Log.e(TAG, "", re); 462 } 463 } 464 } 465 mServiceListener = null; 466 } 467 468 /** 469 * Connects to remote device. 470 * 471 * Currently, the system supports only 1 connection. So, in case of the 472 * second connection, this implementation will disconnect already connected 473 * device automatically and will process the new one. 474 * 475 * @param device a remote device we want connect to 476 * @return <code>true</code> if command has been issued successfully; <code>false</code> 477 * otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent. 478 */ 479 public boolean connect(BluetoothDevice device) { 480 if (DBG) log("connect(" + device + ")"); 481 final IBluetoothHeadsetClient service = mService; 482 if (service != null && isEnabled() && isValidDevice(device)) { 483 try { 484 return service.connect(device); 485 } catch (RemoteException e) { 486 Log.e(TAG, Log.getStackTraceString(new Throwable())); 487 return false; 488 } 489 } 490 if (service == null) Log.w(TAG, "Proxy not attached to service"); 491 return false; 492 } 493 494 /** 495 * Disconnects remote device 496 * 497 * @param device a remote device we want disconnect 498 * @return <code>true</code> if command has been issued successfully; <code>false</code> 499 * otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent. 500 */ 501 public boolean disconnect(BluetoothDevice device) { 502 if (DBG) log("disconnect(" + device + ")"); 503 final IBluetoothHeadsetClient service = mService; 504 if (service != null && isEnabled() && isValidDevice(device)) { 505 try { 506 return service.disconnect(device); 507 } catch (RemoteException e) { 508 Log.e(TAG, Log.getStackTraceString(new Throwable())); 509 return false; 510 } 511 } 512 if (service == null) Log.w(TAG, "Proxy not attached to service"); 513 return false; 514 } 515 516 /** 517 * Return the list of connected remote devices 518 * 519 * @return list of connected devices; empty list if nothing is connected. 520 */ 521 @Override 522 public List<BluetoothDevice> getConnectedDevices() { 523 if (VDBG) log("getConnectedDevices()"); 524 final IBluetoothHeadsetClient service = mService; 525 if (service != null && isEnabled()) { 526 try { 527 return service.getConnectedDevices(); 528 } catch (RemoteException e) { 529 Log.e(TAG, Log.getStackTraceString(new Throwable())); 530 return new ArrayList<BluetoothDevice>(); 531 } 532 } 533 if (service == null) Log.w(TAG, "Proxy not attached to service"); 534 return new ArrayList<BluetoothDevice>(); 535 } 536 537 /** 538 * Returns list of remote devices in a particular state 539 * 540 * @param states collection of states 541 * @return list of devices that state matches the states listed in <code>states</code>; empty 542 * list if nothing matches the <code>states</code> 543 */ 544 @Override 545 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 546 if (VDBG) log("getDevicesMatchingStates()"); 547 final IBluetoothHeadsetClient service = mService; 548 if (service != null && isEnabled()) { 549 try { 550 return service.getDevicesMatchingConnectionStates(states); 551 } catch (RemoteException e) { 552 Log.e(TAG, Log.getStackTraceString(new Throwable())); 553 return new ArrayList<BluetoothDevice>(); 554 } 555 } 556 if (service == null) Log.w(TAG, "Proxy not attached to service"); 557 return new ArrayList<BluetoothDevice>(); 558 } 559 560 /** 561 * Returns state of the <code>device</code> 562 * 563 * @param device a remote device 564 * @return the state of connection of the device 565 */ 566 @Override 567 public int getConnectionState(BluetoothDevice device) { 568 if (VDBG) log("getConnectionState(" + device + ")"); 569 final IBluetoothHeadsetClient service = mService; 570 if (service != null && isEnabled() && isValidDevice(device)) { 571 try { 572 return service.getConnectionState(device); 573 } catch (RemoteException e) { 574 Log.e(TAG, Log.getStackTraceString(new Throwable())); 575 return BluetoothProfile.STATE_DISCONNECTED; 576 } 577 } 578 if (service == null) Log.w(TAG, "Proxy not attached to service"); 579 return BluetoothProfile.STATE_DISCONNECTED; 580 } 581 582 /** 583 * Set priority of the profile 584 * 585 * The device should already be paired. 586 */ 587 public boolean setPriority(BluetoothDevice device, int priority) { 588 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 589 final IBluetoothHeadsetClient service = mService; 590 if (service != null && isEnabled() && isValidDevice(device)) { 591 if (priority != BluetoothProfile.PRIORITY_OFF 592 && priority != BluetoothProfile.PRIORITY_ON) { 593 return false; 594 } 595 try { 596 return service.setPriority(device, priority); 597 } catch (RemoteException e) { 598 Log.e(TAG, Log.getStackTraceString(new Throwable())); 599 return false; 600 } 601 } 602 if (service == null) Log.w(TAG, "Proxy not attached to service"); 603 return false; 604 } 605 606 /** 607 * Get the priority of the profile. 608 */ 609 public int getPriority(BluetoothDevice device) { 610 if (VDBG) log("getPriority(" + device + ")"); 611 final IBluetoothHeadsetClient service = mService; 612 if (service != null && isEnabled() && isValidDevice(device)) { 613 try { 614 return service.getPriority(device); 615 } catch (RemoteException e) { 616 Log.e(TAG, Log.getStackTraceString(new Throwable())); 617 return PRIORITY_OFF; 618 } 619 } 620 if (service == null) Log.w(TAG, "Proxy not attached to service"); 621 return PRIORITY_OFF; 622 } 623 624 /** 625 * Starts voice recognition. 626 * 627 * @param device remote device 628 * @return <code>true</code> if command has been issued successfully; <code>false</code> 629 * otherwise; upon completion HFP sends {@link #ACTION_AG_EVENT} intent. 630 * 631 * <p>Feature required for successful execution is being reported by: {@link 632 * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature 633 * is not supported.</p> 634 */ 635 public boolean startVoiceRecognition(BluetoothDevice device) { 636 if (DBG) log("startVoiceRecognition()"); 637 final IBluetoothHeadsetClient service = mService; 638 if (service != null && isEnabled() && isValidDevice(device)) { 639 try { 640 return service.startVoiceRecognition(device); 641 } catch (RemoteException e) { 642 Log.e(TAG, Log.getStackTraceString(new Throwable())); 643 } 644 } 645 if (service == null) Log.w(TAG, "Proxy not attached to service"); 646 return false; 647 } 648 649 /** 650 * Stops voice recognition. 651 * 652 * @param device remote device 653 * @return <code>true</code> if command has been issued successfully; <code>false</code> 654 * otherwise; upon completion HFP sends {@link #ACTION_AG_EVENT} intent. 655 * 656 * <p>Feature required for successful execution is being reported by: {@link 657 * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature 658 * is not supported.</p> 659 */ 660 public boolean stopVoiceRecognition(BluetoothDevice device) { 661 if (DBG) log("stopVoiceRecognition()"); 662 final IBluetoothHeadsetClient service = mService; 663 if (service != null && isEnabled() && isValidDevice(device)) { 664 try { 665 return service.stopVoiceRecognition(device); 666 } catch (RemoteException e) { 667 Log.e(TAG, Log.getStackTraceString(new Throwable())); 668 } 669 } 670 if (service == null) Log.w(TAG, "Proxy not attached to service"); 671 return false; 672 } 673 674 /** 675 * Returns list of all calls in any state. 676 * 677 * @param device remote device 678 * @return list of calls; empty list if none call exists 679 */ 680 public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) { 681 if (DBG) log("getCurrentCalls()"); 682 final IBluetoothHeadsetClient service = mService; 683 if (service != null && isEnabled() && isValidDevice(device)) { 684 try { 685 return service.getCurrentCalls(device); 686 } catch (RemoteException e) { 687 Log.e(TAG, Log.getStackTraceString(new Throwable())); 688 } 689 } 690 if (service == null) Log.w(TAG, "Proxy not attached to service"); 691 return null; 692 } 693 694 /** 695 * Returns list of current values of AG indicators. 696 * 697 * @param device remote device 698 * @return bundle of AG indicators; null if device is not in CONNECTED state 699 */ 700 public Bundle getCurrentAgEvents(BluetoothDevice device) { 701 if (DBG) log("getCurrentCalls()"); 702 final IBluetoothHeadsetClient service = mService; 703 if (service != null && isEnabled() && isValidDevice(device)) { 704 try { 705 return service.getCurrentAgEvents(device); 706 } catch (RemoteException e) { 707 Log.e(TAG, Log.getStackTraceString(new Throwable())); 708 } 709 } 710 if (service == null) Log.w(TAG, "Proxy not attached to service"); 711 return null; 712 } 713 714 /** 715 * Accepts a call 716 * 717 * @param device remote device 718 * @param flag action policy while accepting a call. Possible values {@link #CALL_ACCEPT_NONE}, 719 * {@link #CALL_ACCEPT_HOLD}, {@link #CALL_ACCEPT_TERMINATE} 720 * @return <code>true</code> if command has been issued successfully; <code>false</code> 721 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. 722 */ 723 public boolean acceptCall(BluetoothDevice device, int flag) { 724 if (DBG) log("acceptCall()"); 725 final IBluetoothHeadsetClient service = mService; 726 if (service != null && isEnabled() && isValidDevice(device)) { 727 try { 728 return service.acceptCall(device, flag); 729 } catch (RemoteException e) { 730 Log.e(TAG, Log.getStackTraceString(new Throwable())); 731 } 732 } 733 if (service == null) Log.w(TAG, "Proxy not attached to service"); 734 return false; 735 } 736 737 /** 738 * Holds a call. 739 * 740 * @param device remote device 741 * @return <code>true</code> if command has been issued successfully; <code>false</code> 742 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. 743 */ 744 public boolean holdCall(BluetoothDevice device) { 745 if (DBG) log("holdCall()"); 746 final IBluetoothHeadsetClient service = mService; 747 if (service != null && isEnabled() && isValidDevice(device)) { 748 try { 749 return service.holdCall(device); 750 } catch (RemoteException e) { 751 Log.e(TAG, Log.getStackTraceString(new Throwable())); 752 } 753 } 754 if (service == null) Log.w(TAG, "Proxy not attached to service"); 755 return false; 756 } 757 758 /** 759 * Rejects a call. 760 * 761 * @param device remote device 762 * @return <code>true</code> if command has been issued successfully; <code>false</code> 763 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. 764 * 765 * <p>Feature required for successful execution is being reported by: {@link 766 * #EXTRA_AG_FEATURE_REJECT_CALL}. This method invocation will fail silently when feature is not 767 * supported.</p> 768 */ 769 public boolean rejectCall(BluetoothDevice device) { 770 if (DBG) log("rejectCall()"); 771 final IBluetoothHeadsetClient service = mService; 772 if (service != null && isEnabled() && isValidDevice(device)) { 773 try { 774 return service.rejectCall(device); 775 } catch (RemoteException e) { 776 Log.e(TAG, Log.getStackTraceString(new Throwable())); 777 } 778 } 779 if (service == null) Log.w(TAG, "Proxy not attached to service"); 780 return false; 781 } 782 783 /** 784 * Terminates a specified call. 785 * 786 * Works only when Extended Call Control is supported by Audio Gateway. 787 * 788 * @param device remote device 789 * @param call Handle of call obtained in {@link #dial(BluetoothDevice, String)} or obtained via 790 * {@link #ACTION_CALL_CHANGED}. {@code call} may be null in which case we will hangup all active 791 * calls. 792 * @return <code>true</code> if command has been issued successfully; <code>false</code> 793 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. 794 * 795 * <p>Feature required for successful execution is being reported by: {@link 796 * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not 797 * supported.</p> 798 */ 799 public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call) { 800 if (DBG) log("terminateCall()"); 801 final IBluetoothHeadsetClient service = mService; 802 if (service != null && isEnabled() && isValidDevice(device)) { 803 try { 804 return service.terminateCall(device, call); 805 } catch (RemoteException e) { 806 Log.e(TAG, Log.getStackTraceString(new Throwable())); 807 } 808 } 809 if (service == null) Log.w(TAG, "Proxy not attached to service"); 810 return false; 811 } 812 813 /** 814 * Enters private mode with a specified call. 815 * 816 * Works only when Extended Call Control is supported by Audio Gateway. 817 * 818 * @param device remote device 819 * @param index index of the call to connect in private mode 820 * @return <code>true</code> if command has been issued successfully; <code>false</code> 821 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. 822 * 823 * <p>Feature required for successful execution is being reported by: {@link 824 * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not 825 * supported.</p> 826 */ 827 public boolean enterPrivateMode(BluetoothDevice device, int index) { 828 if (DBG) log("enterPrivateMode()"); 829 final IBluetoothHeadsetClient service = mService; 830 if (service != null && isEnabled() && isValidDevice(device)) { 831 try { 832 return service.enterPrivateMode(device, index); 833 } catch (RemoteException e) { 834 Log.e(TAG, Log.getStackTraceString(new Throwable())); 835 } 836 } 837 if (service == null) Log.w(TAG, "Proxy not attached to service"); 838 return false; 839 } 840 841 /** 842 * Performs explicit call transfer. 843 * 844 * That means connect other calls and disconnect. 845 * 846 * @param device remote device 847 * @return <code>true</code> if command has been issued successfully; <code>false</code> 848 * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. 849 * 850 * <p>Feature required for successful execution is being reported by: {@link 851 * #EXTRA_AG_FEATURE_MERGE_AND_DETACH}. This method invocation will fail silently when feature 852 * is not supported.</p> 853 */ 854 public boolean explicitCallTransfer(BluetoothDevice device) { 855 if (DBG) log("explicitCallTransfer()"); 856 final IBluetoothHeadsetClient service = mService; 857 if (service != null && isEnabled() && isValidDevice(device)) { 858 try { 859 return service.explicitCallTransfer(device); 860 } catch (RemoteException e) { 861 Log.e(TAG, Log.getStackTraceString(new Throwable())); 862 } 863 } 864 if (service == null) Log.w(TAG, "Proxy not attached to service"); 865 return false; 866 } 867 868 /** 869 * Places a call with specified number. 870 * 871 * @param device remote device 872 * @param number valid phone number 873 * @return <code>{@link BluetoothHeadsetClientCall} call</code> if command has been issued 874 * successfully; <code>{@link null}</code> otherwise; upon completion HFP sends {@link 875 * #ACTION_CALL_CHANGED} intent in case of success; {@link #ACTION_RESULT} is sent otherwise; 876 */ 877 public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) { 878 if (DBG) log("dial()"); 879 final IBluetoothHeadsetClient service = mService; 880 if (service != null && isEnabled() && isValidDevice(device)) { 881 try { 882 return service.dial(device, number); 883 } catch (RemoteException e) { 884 Log.e(TAG, Log.getStackTraceString(new Throwable())); 885 } 886 } 887 if (service == null) Log.w(TAG, "Proxy not attached to service"); 888 return null; 889 } 890 891 /** 892 * Sends DTMF code. 893 * 894 * Possible code values : 0,1,2,3,4,5,6,7,8,9,A,B,C,D,*,# 895 * 896 * @param device remote device 897 * @param code ASCII code 898 * @return <code>true</code> if command has been issued successfully; <code>false</code> 899 * otherwise; upon completion HFP sends {@link #ACTION_RESULT} intent; 900 */ 901 public boolean sendDTMF(BluetoothDevice device, byte code) { 902 if (DBG) log("sendDTMF()"); 903 final IBluetoothHeadsetClient service = mService; 904 if (service != null && isEnabled() && isValidDevice(device)) { 905 try { 906 return service.sendDTMF(device, code); 907 } catch (RemoteException e) { 908 Log.e(TAG, Log.getStackTraceString(new Throwable())); 909 } 910 } 911 if (service == null) Log.w(TAG, "Proxy not attached to service"); 912 return false; 913 } 914 915 /** 916 * Get a number corresponding to last voice tag recorded on AG. 917 * 918 * @param device remote device 919 * @return <code>true</code> if command has been issued successfully; <code>false</code> 920 * otherwise; upon completion HFP sends {@link #ACTION_LAST_VTAG} or {@link #ACTION_RESULT} 921 * intent; 922 * 923 * <p>Feature required for successful execution is being reported by: {@link 924 * #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT}. This method invocation will fail silently when 925 * feature is not supported.</p> 926 */ 927 public boolean getLastVoiceTagNumber(BluetoothDevice device) { 928 if (DBG) log("getLastVoiceTagNumber()"); 929 final IBluetoothHeadsetClient service = mService; 930 if (service != null && isEnabled() && isValidDevice(device)) { 931 try { 932 return service.getLastVoiceTagNumber(device); 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 * Returns current audio state of Audio Gateway. 943 * 944 * Note: This is an internal function and shouldn't be exposed 945 */ 946 public int getAudioState(BluetoothDevice device) { 947 if (VDBG) log("getAudioState"); 948 final IBluetoothHeadsetClient service = mService; 949 if (service != null && isEnabled()) { 950 try { 951 return service.getAudioState(device); 952 } catch (RemoteException e) { 953 Log.e(TAG, e.toString()); 954 } 955 } else { 956 Log.w(TAG, "Proxy not attached to service"); 957 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 958 } 959 return BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED; 960 } 961 962 /** 963 * Sets whether audio routing is allowed. 964 * 965 * @param device remote device 966 * @param allowed if routing is allowed to the device Note: This is an internal function and 967 * shouldn't be exposed 968 */ 969 public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) { 970 if (VDBG) log("setAudioRouteAllowed"); 971 final IBluetoothHeadsetClient service = mService; 972 if (service != null && isEnabled()) { 973 try { 974 service.setAudioRouteAllowed(device, allowed); 975 } catch (RemoteException e) { 976 Log.e(TAG, e.toString()); 977 } 978 } else { 979 Log.w(TAG, "Proxy not attached to service"); 980 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 981 } 982 } 983 984 /** 985 * Returns whether audio routing is allowed. 986 * 987 * @param device remote device 988 * @return whether the command succeeded Note: This is an internal function and shouldn't be 989 * exposed 990 */ 991 public boolean getAudioRouteAllowed(BluetoothDevice device) { 992 if (VDBG) log("getAudioRouteAllowed"); 993 final IBluetoothHeadsetClient service = mService; 994 if (service != null && isEnabled()) { 995 try { 996 return service.getAudioRouteAllowed(device); 997 } catch (RemoteException e) { 998 Log.e(TAG, e.toString()); 999 } 1000 } else { 1001 Log.w(TAG, "Proxy not attached to service"); 1002 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 1003 } 1004 return false; 1005 } 1006 1007 /** 1008 * Initiates a connection of audio channel. 1009 * 1010 * It setup SCO channel with remote connected Handsfree AG device. 1011 * 1012 * @param device remote device 1013 * @return <code>true</code> if command has been issued successfully; <code>false</code> 1014 * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent; 1015 */ 1016 public boolean connectAudio(BluetoothDevice device) { 1017 final IBluetoothHeadsetClient service = mService; 1018 if (service != null && isEnabled()) { 1019 try { 1020 return service.connectAudio(device); 1021 } catch (RemoteException e) { 1022 Log.e(TAG, e.toString()); 1023 } 1024 } else { 1025 Log.w(TAG, "Proxy not attached to service"); 1026 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 1027 } 1028 return false; 1029 } 1030 1031 /** 1032 * Disconnects audio channel. 1033 * 1034 * It tears down the SCO channel from remote AG device. 1035 * 1036 * @param device remote device 1037 * @return <code>true</code> if command has been issued successfully; <code>false</code> 1038 * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent; 1039 */ 1040 public boolean disconnectAudio(BluetoothDevice device) { 1041 final IBluetoothHeadsetClient service = mService; 1042 if (service != null && isEnabled()) { 1043 try { 1044 return service.disconnectAudio(device); 1045 } catch (RemoteException e) { 1046 Log.e(TAG, e.toString()); 1047 } 1048 } else { 1049 Log.w(TAG, "Proxy not attached to service"); 1050 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 1051 } 1052 return false; 1053 } 1054 1055 /** 1056 * Get Audio Gateway features 1057 * 1058 * @param device remote device 1059 * @return bundle of AG features; null if no service or AG not connected 1060 */ 1061 public Bundle getCurrentAgFeatures(BluetoothDevice device) { 1062 final IBluetoothHeadsetClient service = mService; 1063 if (service != null && isEnabled()) { 1064 try { 1065 return service.getCurrentAgFeatures(device); 1066 } catch (RemoteException e) { 1067 Log.e(TAG, e.toString()); 1068 } 1069 } else { 1070 Log.w(TAG, "Proxy not attached to service"); 1071 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 1072 } 1073 return null; 1074 } 1075 1076 1077 private final ServiceConnection mConnection = new ServiceConnection() { 1078 @Override 1079 public void onServiceConnected(ComponentName className, IBinder service) { 1080 if (DBG) Log.d(TAG, "Proxy object connected"); 1081 mService = IBluetoothHeadsetClient.Stub.asInterface(Binder.allowBlocking(service)); 1082 1083 if (mServiceListener != null) { 1084 mServiceListener.onServiceConnected(BluetoothProfile.HEADSET_CLIENT, 1085 BluetoothHeadsetClient.this); 1086 } 1087 } 1088 1089 @Override 1090 public void onServiceDisconnected(ComponentName className) { 1091 if (DBG) Log.d(TAG, "Proxy object disconnected"); 1092 mService = null; 1093 if (mServiceListener != null) { 1094 mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET_CLIENT); 1095 } 1096 } 1097 }; 1098 1099 private boolean isEnabled() { 1100 return mAdapter.getState() == BluetoothAdapter.STATE_ON; 1101 } 1102 1103 private static boolean isValidDevice(BluetoothDevice device) { 1104 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); 1105 } 1106 1107 private static void log(String msg) { 1108 Log.d(TAG, msg); 1109 } 1110} 1111