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