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