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
17package android.bluetooth;
18
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.ServiceConnection;
23import android.os.Bundle;
24import android.os.IBinder;
25import android.os.RemoteException;
26import android.util.Log;
27
28import java.util.ArrayList;
29import 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 * */
40public 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     */
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
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     */
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     */
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     */
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
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
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
1152    private boolean isEnabled() {
1153       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
1154       return false;
1155    }
1156
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
1164    private static void log(String msg) {
1165        Log.d(TAG, msg);
1166    }
1167}
1168