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