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