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