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