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