1/*
2 * Copyright (C) 2008 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.Manifest;
20import android.annotation.Nullable;
21import android.annotation.RequiresPermission;
22import android.annotation.SdkConstant;
23import android.annotation.SdkConstant.SdkConstantType;
24import android.annotation.SystemApi;
25import android.content.ComponentName;
26import android.content.Context;
27import android.os.Binder;
28import android.os.Handler;
29import android.os.IBinder;
30import android.os.Looper;
31import android.os.Message;
32import android.os.RemoteException;
33import android.util.Log;
34
35import java.util.ArrayList;
36import java.util.List;
37
38/**
39 * Public API for controlling the Bluetooth Headset Service. This includes both
40 * Bluetooth Headset and Handsfree (v1.5) profiles.
41 *
42 * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
43 * Service via IPC.
44 *
45 * <p> Use {@link BluetoothAdapter#getProfileProxy} to get
46 * the BluetoothHeadset proxy object. Use
47 * {@link BluetoothAdapter#closeProfileProxy} to close the service connection.
48 *
49 * <p> Android only supports one connected Bluetooth Headset at a time.
50 * Each method is protected with its appropriate permission.
51 */
52public final class BluetoothHeadset implements BluetoothProfile {
53    private static final String TAG = "BluetoothHeadset";
54    private static final boolean DBG = true;
55    private static final boolean VDBG = false;
56
57    /**
58     * Intent used to broadcast the change in connection state of the Headset
59     * profile.
60     *
61     * <p>This intent will have 3 extras:
62     * <ul>
63     * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
64     * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
65     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
66     * </ul>
67     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
68     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
69     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
70     *
71     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
72     * receive.
73     */
74    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
75    public static final String ACTION_CONNECTION_STATE_CHANGED =
76            "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
77
78    /**
79     * Intent used to broadcast the change in the Audio Connection state of the
80     * A2DP profile.
81     *
82     * <p>This intent will have 3 extras:
83     * <ul>
84     * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
85     * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
86     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
87     * </ul>
88     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
89     * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED},
90     *
91     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
92     * to receive.
93     */
94    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
95    public static final String ACTION_AUDIO_STATE_CHANGED =
96            "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
97
98    /**
99     * Intent used to broadcast the selection of a connected device as active.
100     *
101     * <p>This intent will have one extra:
102     * <ul>
103     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can
104     * be null if no device is active. </li>
105     * </ul>
106     *
107     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
108     * receive.
109     *
110     * @hide
111     */
112    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
113    public static final String ACTION_ACTIVE_DEVICE_CHANGED =
114            "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED";
115
116    /**
117     * Intent used to broadcast that the headset has posted a
118     * vendor-specific event.
119     *
120     * <p>This intent will have 4 extras and 1 category.
121     * <ul>
122     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device
123     * </li>
124     * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor
125     * specific command </li>
126     * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT
127     * command type which can be one of  {@link #AT_CMD_TYPE_READ},
128     * {@link #AT_CMD_TYPE_TEST}, or {@link #AT_CMD_TYPE_SET},
129     * {@link #AT_CMD_TYPE_BASIC},{@link #AT_CMD_TYPE_ACTION}. </li>
130     * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command
131     * arguments. </li>
132     * </ul>
133     *
134     * <p> The category is the Company ID of the vendor defining the
135     * vendor-specific command. {@link BluetoothAssignedNumbers}
136     *
137     * For example, for Plantronics specific events
138     * Category will be {@link #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55
139     *
140     * <p> For example, an AT+XEVENT=foo,3 will get translated into
141     * <ul>
142     * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT </li>
143     * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li>
144     * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li>
145     * </ul>
146     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
147     * to receive.
148     */
149    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
150    public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT =
151            "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
152
153    /**
154     * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
155     * intents that contains the name of the vendor-specific command.
156     */
157    public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD =
158            "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD";
159
160    /**
161     * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
162     * intents that contains the AT command type of the vendor-specific command.
163     */
164    public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE =
165            "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE";
166
167    /**
168     * AT command type READ used with
169     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
170     * For example, AT+VGM?. There are no arguments for this command type.
171     */
172    public static final int AT_CMD_TYPE_READ = 0;
173
174    /**
175     * AT command type TEST used with
176     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
177     * For example, AT+VGM=?. There are no arguments for this command type.
178     */
179    public static final int AT_CMD_TYPE_TEST = 1;
180
181    /**
182     * AT command type SET used with
183     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
184     * For example, AT+VGM=<args>.
185     */
186    public static final int AT_CMD_TYPE_SET = 2;
187
188    /**
189     * AT command type BASIC used with
190     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
191     * For example, ATD. Single character commands and everything following the
192     * character are arguments.
193     */
194    public static final int AT_CMD_TYPE_BASIC = 3;
195
196    /**
197     * AT command type ACTION used with
198     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
199     * For example, AT+CHUP. There are no arguments for action commands.
200     */
201    public static final int AT_CMD_TYPE_ACTION = 4;
202
203    /**
204     * A Parcelable String array extra field in
205     * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains
206     * the arguments to the vendor-specific command.
207     */
208    public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS =
209            "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS";
210
211    /**
212     * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
213     * for the companyId
214     */
215    public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY =
216            "android.bluetooth.headset.intent.category.companyid";
217
218    /**
219     * A vendor-specific command for unsolicited result code.
220     */
221    public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID";
222
223    /**
224     * A vendor-specific AT command
225     *
226     * @hide
227     */
228    public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XAPL = "+XAPL";
229
230    /**
231     * A vendor-specific AT command
232     *
233     * @hide
234     */
235    public static final String VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV = "+IPHONEACCEV";
236
237    /**
238     * Battery level indicator associated with
239     * {@link #VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV}
240     *
241     * @hide
242     */
243    public static final int VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL = 1;
244
245    /**
246     * A vendor-specific AT command
247     *
248     * @hide
249     */
250    public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT = "+XEVENT";
251
252    /**
253     * Battery level indicator associated with {@link #VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT}
254     *
255     * @hide
256     */
257    public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL = "BATTERY";
258
259    /**
260     * Headset state when SCO audio is not connected.
261     * This state can be one of
262     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
263     * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
264     */
265    public static final int STATE_AUDIO_DISCONNECTED = 10;
266
267    /**
268     * Headset state when SCO audio is connecting.
269     * This state can be one of
270     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
271     * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
272     */
273    public static final int STATE_AUDIO_CONNECTING = 11;
274
275    /**
276     * Headset state when SCO audio is connected.
277     * This state can be one of
278     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
279     * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
280     */
281
282    /**
283     * Intent used to broadcast the headset's indicator status
284     *
285     * <p>This intent will have 3 extras:
286     * <ul>
287     * <li> {@link #EXTRA_HF_INDICATORS_IND_ID} - The Assigned number of headset Indicator which
288     * is supported by the headset ( as indicated by AT+BIND command in the SLC
289     * sequence) or whose value is changed (indicated by AT+BIEV command) </li>
290     * <li> {@link #EXTRA_HF_INDICATORS_IND_VALUE} - Updated value of headset indicator. </li>
291     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - Remote device. </li>
292     * </ul>
293     * <p>{@link #EXTRA_HF_INDICATORS_IND_ID} is defined by Bluetooth SIG and each of the indicators
294     * are given an assigned number. Below shows the assigned number of Indicator added so far
295     * - Enhanced Safety - 1, Valid Values: 0 - Disabled, 1 - Enabled
296     * - Battery Level - 2, Valid Values: 0~100 - Remaining level of Battery
297     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to receive.
298     *
299     * @hide
300     */
301    public static final String ACTION_HF_INDICATORS_VALUE_CHANGED =
302            "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED";
303
304    /**
305     * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
306     * intents that contains the assigned number of the headset indicator as defined by
307     * Bluetooth SIG that is being sent. Value range is 0-65535 as defined in HFP 1.7
308     *
309     * @hide
310     */
311    public static final String EXTRA_HF_INDICATORS_IND_ID =
312            "android.bluetooth.headset.extra.HF_INDICATORS_IND_ID";
313
314    /**
315     * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
316     * intents that contains the value of the Headset indicator that is being sent.
317     *
318     * @hide
319     */
320    public static final String EXTRA_HF_INDICATORS_IND_VALUE =
321            "android.bluetooth.headset.extra.HF_INDICATORS_IND_VALUE";
322
323    public static final int STATE_AUDIO_CONNECTED = 12;
324
325    private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100;
326    private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101;
327
328    private Context mContext;
329    private ServiceListener mServiceListener;
330    private volatile IBluetoothHeadset mService;
331    private BluetoothAdapter mAdapter;
332
333    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
334            new IBluetoothStateChangeCallback.Stub() {
335                public void onBluetoothStateChange(boolean up) {
336                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
337                    if (!up) {
338                        if (VDBG) Log.d(TAG, "Unbinding service...");
339                        doUnbind();
340                    } else {
341                        synchronized (mConnection) {
342                            try {
343                                if (mService == null) {
344                                    if (VDBG) Log.d(TAG, "Binding service...");
345                                    doBind();
346                                }
347                            } catch (Exception re) {
348                                Log.e(TAG, "", re);
349                            }
350                        }
351                    }
352                }
353            };
354
355    /**
356     * Create a BluetoothHeadset proxy object.
357     */
358    /*package*/ BluetoothHeadset(Context context, ServiceListener l) {
359        mContext = context;
360        mServiceListener = l;
361        mAdapter = BluetoothAdapter.getDefaultAdapter();
362
363        IBluetoothManager mgr = mAdapter.getBluetoothManager();
364        if (mgr != null) {
365            try {
366                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
367            } catch (RemoteException e) {
368                Log.e(TAG, "", e);
369            }
370        }
371
372        doBind();
373    }
374
375    boolean doBind() {
376        try {
377            return mAdapter.getBluetoothManager().bindBluetoothProfileService(
378                    BluetoothProfile.HEADSET, mConnection);
379        } catch (RemoteException e) {
380            Log.e(TAG, "Unable to bind HeadsetService", e);
381        }
382        return false;
383    }
384
385    void doUnbind() {
386        synchronized (mConnection) {
387            if (mService != null) {
388                try {
389                    mAdapter.getBluetoothManager().unbindBluetoothProfileService(
390                            BluetoothProfile.HEADSET, mConnection);
391                } catch (RemoteException e) {
392                    Log.e(TAG, "Unable to unbind HeadsetService", e);
393                }
394            }
395        }
396    }
397
398    /**
399     * Close the connection to the backing service.
400     * Other public functions of BluetoothHeadset will return default error
401     * results once close() has been called. Multiple invocations of close()
402     * are ok.
403     */
404    /*package*/ void close() {
405        if (VDBG) log("close()");
406
407        IBluetoothManager mgr = mAdapter.getBluetoothManager();
408        if (mgr != null) {
409            try {
410                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
411            } catch (Exception e) {
412                Log.e(TAG, "", e);
413            }
414        }
415        mServiceListener = null;
416        doUnbind();
417    }
418
419    /**
420     * Initiate connection to a profile of the remote bluetooth device.
421     *
422     * <p> Currently, the system supports only 1 connection to the
423     * headset/handsfree profile. The API will automatically disconnect connected
424     * devices before connecting.
425     *
426     * <p> This API returns false in scenarios like the profile on the
427     * device is already connected or Bluetooth is not turned on.
428     * When this API returns true, it is guaranteed that
429     * connection state intent for the profile will be broadcasted with
430     * the state. Users can get the connection state of the profile
431     * from this intent.
432     *
433     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
434     * permission.
435     *
436     * @param device Remote Bluetooth Device
437     * @return false on immediate error, true otherwise
438     * @hide
439     */
440    @SystemApi
441    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
442    public boolean connect(BluetoothDevice device) {
443        if (DBG) log("connect(" + device + ")");
444        final IBluetoothHeadset service = mService;
445        if (service != null && isEnabled() && isValidDevice(device)) {
446            try {
447                return service.connect(device);
448            } catch (RemoteException e) {
449                Log.e(TAG, Log.getStackTraceString(new Throwable()));
450                return false;
451            }
452        }
453        if (service == null) Log.w(TAG, "Proxy not attached to service");
454        return false;
455    }
456
457    /**
458     * Initiate disconnection from a profile
459     *
460     * <p> This API will return false in scenarios like the profile on the
461     * Bluetooth device is not in connected state etc. When this API returns,
462     * true, it is guaranteed that the connection state change
463     * intent will be broadcasted with the state. Users can get the
464     * disconnection state of the profile from this intent.
465     *
466     * <p> If the disconnection is initiated by a remote device, the state
467     * will transition from {@link #STATE_CONNECTED} to
468     * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
469     * host (local) device the state will transition from
470     * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
471     * state {@link #STATE_DISCONNECTED}. The transition to
472     * {@link #STATE_DISCONNECTING} can be used to distinguish between the
473     * two scenarios.
474     *
475     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
476     * permission.
477     *
478     * @param device Remote Bluetooth Device
479     * @return false on immediate error, true otherwise
480     * @hide
481     */
482    @SystemApi
483    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
484    public boolean disconnect(BluetoothDevice device) {
485        if (DBG) log("disconnect(" + device + ")");
486        final IBluetoothHeadset service = mService;
487        if (service != null && isEnabled() && isValidDevice(device)) {
488            try {
489                return service.disconnect(device);
490            } catch (RemoteException e) {
491                Log.e(TAG, Log.getStackTraceString(new Throwable()));
492                return false;
493            }
494        }
495        if (service == null) Log.w(TAG, "Proxy not attached to service");
496        return false;
497    }
498
499    /**
500     * {@inheritDoc}
501     */
502    @Override
503    public List<BluetoothDevice> getConnectedDevices() {
504        if (VDBG) log("getConnectedDevices()");
505        final IBluetoothHeadset service = mService;
506        if (service != null && isEnabled()) {
507            try {
508                return service.getConnectedDevices();
509            } catch (RemoteException e) {
510                Log.e(TAG, Log.getStackTraceString(new Throwable()));
511                return new ArrayList<BluetoothDevice>();
512            }
513        }
514        if (service == null) Log.w(TAG, "Proxy not attached to service");
515        return new ArrayList<BluetoothDevice>();
516    }
517
518    /**
519     * {@inheritDoc}
520     */
521    @Override
522    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
523        if (VDBG) log("getDevicesMatchingStates()");
524        final IBluetoothHeadset service = mService;
525        if (service != null && isEnabled()) {
526            try {
527                return service.getDevicesMatchingConnectionStates(states);
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     * {@inheritDoc}
539     */
540    @Override
541    public int getConnectionState(BluetoothDevice device) {
542        if (VDBG) log("getConnectionState(" + device + ")");
543        final IBluetoothHeadset service = mService;
544        if (service != null && isEnabled() && isValidDevice(device)) {
545            try {
546                return service.getConnectionState(device);
547            } catch (RemoteException e) {
548                Log.e(TAG, Log.getStackTraceString(new Throwable()));
549                return BluetoothProfile.STATE_DISCONNECTED;
550            }
551        }
552        if (service == null) Log.w(TAG, "Proxy not attached to service");
553        return BluetoothProfile.STATE_DISCONNECTED;
554    }
555
556    /**
557     * Set priority of the profile
558     *
559     * <p> The device should already be paired.
560     * Priority can be one of {@link BluetoothProfile#PRIORITY_ON} or
561     * {@link BluetoothProfile#PRIORITY_OFF},
562     *
563     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
564     * permission.
565     *
566     * @param device Paired bluetooth device
567     * @param priority
568     * @return true if priority is set, false on error
569     * @hide
570     */
571    @SystemApi
572    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
573    public boolean setPriority(BluetoothDevice device, int priority) {
574        if (DBG) log("setPriority(" + device + ", " + priority + ")");
575        final IBluetoothHeadset service = mService;
576        if (service != null && isEnabled() && isValidDevice(device)) {
577            if (priority != BluetoothProfile.PRIORITY_OFF
578                    && priority != BluetoothProfile.PRIORITY_ON) {
579                return false;
580            }
581            try {
582                return service.setPriority(device, priority);
583            } catch (RemoteException e) {
584                Log.e(TAG, Log.getStackTraceString(new Throwable()));
585                return false;
586            }
587        }
588        if (service == null) Log.w(TAG, "Proxy not attached to service");
589        return false;
590    }
591
592    /**
593     * Get the priority of the profile.
594     *
595     * <p> The priority can be any of:
596     * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
597     * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
598     *
599     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
600     *
601     * @param device Bluetooth device
602     * @return priority of the device
603     * @hide
604     */
605    public int getPriority(BluetoothDevice device) {
606        if (VDBG) log("getPriority(" + device + ")");
607        final IBluetoothHeadset service = mService;
608        if (service != null && isEnabled() && isValidDevice(device)) {
609            try {
610                return service.getPriority(device);
611            } catch (RemoteException e) {
612                Log.e(TAG, Log.getStackTraceString(new Throwable()));
613                return PRIORITY_OFF;
614            }
615        }
616        if (service == null) Log.w(TAG, "Proxy not attached to service");
617        return PRIORITY_OFF;
618    }
619
620    /**
621     * Start Bluetooth voice recognition. This methods sends the voice
622     * recognition AT command to the headset and establishes the
623     * audio connection.
624     *
625     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
626     * If this function returns true, this intent will be broadcasted with
627     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
628     *
629     * <p> {@link #EXTRA_STATE} will transition from
630     * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
631     * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
632     * in case of failure to establish the audio connection.
633     *
634     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
635     *
636     * @param device Bluetooth headset
637     * @return false if there is no headset connected, or the connected headset doesn't support
638     * voice recognition, or voice recognition is already started, or audio channel is occupied,
639     * or on error, true otherwise
640     */
641    public boolean startVoiceRecognition(BluetoothDevice device) {
642        if (DBG) log("startVoiceRecognition()");
643        final IBluetoothHeadset service = mService;
644        if (service != null && isEnabled() && isValidDevice(device)) {
645            try {
646                return service.startVoiceRecognition(device);
647            } catch (RemoteException e) {
648                Log.e(TAG, Log.getStackTraceString(new Throwable()));
649            }
650        }
651        if (service == null) Log.w(TAG, "Proxy not attached to service");
652        return false;
653    }
654
655    /**
656     * Stop Bluetooth Voice Recognition mode, and shut down the
657     * Bluetooth audio path.
658     *
659     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
660     * If this function returns true, this intent will be broadcasted with
661     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
662     *
663     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
664     *
665     * @param device Bluetooth headset
666     * @return false if there is no headset connected, or voice recognition has not started,
667     * or voice recognition has ended on this headset, or on error, true otherwise
668     */
669    public boolean stopVoiceRecognition(BluetoothDevice device) {
670        if (DBG) log("stopVoiceRecognition()");
671        final IBluetoothHeadset service = mService;
672        if (service != null && isEnabled() && isValidDevice(device)) {
673            try {
674                return service.stopVoiceRecognition(device);
675            } catch (RemoteException e) {
676                Log.e(TAG, Log.getStackTraceString(new Throwable()));
677            }
678        }
679        if (service == null) Log.w(TAG, "Proxy not attached to service");
680        return false;
681    }
682
683    /**
684     * Check if Bluetooth SCO audio is connected.
685     *
686     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
687     *
688     * @param device Bluetooth headset
689     * @return true if SCO is connected, false otherwise or on error
690     */
691    public boolean isAudioConnected(BluetoothDevice device) {
692        if (VDBG) log("isAudioConnected()");
693        final IBluetoothHeadset service = mService;
694        if (service != null && isEnabled() && isValidDevice(device)) {
695            try {
696                return service.isAudioConnected(device);
697            } catch (RemoteException e) {
698                Log.e(TAG, Log.getStackTraceString(new Throwable()));
699            }
700        }
701        if (service == null) Log.w(TAG, "Proxy not attached to service");
702        return false;
703    }
704
705    /**
706     * Indicates if current platform supports voice dialing over bluetooth SCO.
707     *
708     * @return true if voice dialing over bluetooth is supported, false otherwise.
709     * @hide
710     */
711    public static boolean isBluetoothVoiceDialingEnabled(Context context) {
712        return context.getResources().getBoolean(
713                com.android.internal.R.bool.config_bluetooth_sco_off_call);
714    }
715
716    /**
717     * Get the current audio state of the Headset.
718     * Note: This is an internal function and shouldn't be exposed
719     *
720     * @hide
721     */
722    public int getAudioState(BluetoothDevice device) {
723        if (VDBG) log("getAudioState");
724        final IBluetoothHeadset service = mService;
725        if (service != null && !isDisabled()) {
726            try {
727                return service.getAudioState(device);
728            } catch (RemoteException e) {
729                Log.e(TAG, e.toString());
730            }
731        } else {
732            Log.w(TAG, "Proxy not attached to service");
733            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
734        }
735        return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
736    }
737
738    /**
739     * Sets whether audio routing is allowed. When set to {@code false}, the AG will not route any
740     * audio to the HF unless explicitly told to.
741     * This method should be used in cases where the SCO channel is shared between multiple profiles
742     * and must be delegated by a source knowledgeable
743     * Note: This is an internal function and shouldn't be exposed
744     *
745     * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise.
746     * @hide
747     */
748    public void setAudioRouteAllowed(boolean allowed) {
749        if (VDBG) log("setAudioRouteAllowed");
750        final IBluetoothHeadset service = mService;
751        if (service != null && isEnabled()) {
752            try {
753                service.setAudioRouteAllowed(allowed);
754            } catch (RemoteException e) {
755                Log.e(TAG, e.toString());
756            }
757        } else {
758            Log.w(TAG, "Proxy not attached to service");
759            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
760        }
761    }
762
763    /**
764     * Returns whether audio routing is allowed. see {@link #setAudioRouteAllowed(boolean)}.
765     * Note: This is an internal function and shouldn't be exposed
766     *
767     * @hide
768     */
769    public boolean getAudioRouteAllowed() {
770        if (VDBG) log("getAudioRouteAllowed");
771        final IBluetoothHeadset service = mService;
772        if (service != null && isEnabled()) {
773            try {
774                return service.getAudioRouteAllowed();
775            } catch (RemoteException e) {
776                Log.e(TAG, e.toString());
777            }
778        } else {
779            Log.w(TAG, "Proxy not attached to service");
780            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
781        }
782        return false;
783    }
784
785    /**
786     * Force SCO audio to be opened regardless any other restrictions
787     *
788     * @param forced Whether or not SCO audio connection should be forced: True to force SCO audio
789     * False to use SCO audio in normal manner
790     * @hide
791     */
792    public void setForceScoAudio(boolean forced) {
793        if (VDBG) log("setForceScoAudio " + String.valueOf(forced));
794        final IBluetoothHeadset service = mService;
795        if (service != null && isEnabled()) {
796            try {
797                service.setForceScoAudio(forced);
798            } catch (RemoteException e) {
799                Log.e(TAG, e.toString());
800            }
801        } else {
802            Log.w(TAG, "Proxy not attached to service");
803            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
804        }
805    }
806
807    /**
808     * Check if at least one headset's SCO audio is connected or connecting
809     *
810     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
811     *
812     * @return true if at least one device's SCO audio is connected or connecting, false otherwise
813     * or on error
814     * @hide
815     */
816    public boolean isAudioOn() {
817        if (VDBG) log("isAudioOn()");
818        final IBluetoothHeadset service = mService;
819        if (service != null && isEnabled()) {
820            try {
821                return service.isAudioOn();
822            } catch (RemoteException e) {
823                Log.e(TAG, Log.getStackTraceString(new Throwable()));
824            }
825        }
826        if (service == null) Log.w(TAG, "Proxy not attached to service");
827        return false;
828
829    }
830
831    /**
832     * Initiates a connection of headset audio to the current active device
833     *
834     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
835     * If this function returns true, this intent will be broadcasted with
836     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
837     *
838     * <p> {@link #EXTRA_STATE} will transition from
839     * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
840     * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
841     * in case of failure to establish the audio connection.
842     *
843     * Note that this intent will not be sent if {@link BluetoothHeadset#isAudioOn()} is true
844     * before calling this method
845     *
846     * @return false if there was some error such as there is no active headset
847     * @hide
848     */
849    public boolean connectAudio() {
850        final IBluetoothHeadset service = mService;
851        if (service != null && isEnabled()) {
852            try {
853                return service.connectAudio();
854            } catch (RemoteException e) {
855                Log.e(TAG, e.toString());
856            }
857        } else {
858            Log.w(TAG, "Proxy not attached to service");
859            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
860        }
861        return false;
862    }
863
864    /**
865     * Initiates a disconnection of HFP SCO audio.
866     * Tear down voice recognition or virtual voice call if any.
867     *
868     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
869     * If this function returns true, this intent will be broadcasted with
870     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
871     *
872     * @return false if audio is not connected, or on error, true otherwise
873     * @hide
874     */
875    public boolean disconnectAudio() {
876        final IBluetoothHeadset service = mService;
877        if (service != null && isEnabled()) {
878            try {
879                return service.disconnectAudio();
880            } catch (RemoteException e) {
881                Log.e(TAG, e.toString());
882            }
883        } else {
884            Log.w(TAG, "Proxy not attached to service");
885            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
886        }
887        return false;
888    }
889
890    /**
891     * Initiates a SCO channel connection as a virtual voice call to the current active device
892     * Active handsfree device will be notified of incoming call and connected call.
893     *
894     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
895     * If this function returns true, this intent will be broadcasted with
896     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
897     *
898     * <p> {@link #EXTRA_STATE} will transition from
899     * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
900     * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
901     * in case of failure to establish the audio connection.
902     *
903     * @return true if successful, false if one of the following case applies
904     *  - SCO audio is not idle (connecting or connected)
905     *  - virtual call has already started
906     *  - there is no active device
907     *  - a Telecom managed call is going on
908     *  - binder is dead or Bluetooth is disabled or other error
909     * @hide
910     */
911    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
912    public boolean startScoUsingVirtualVoiceCall() {
913        if (DBG) log("startScoUsingVirtualVoiceCall()");
914        final IBluetoothHeadset service = mService;
915        if (service != null && isEnabled()) {
916            try {
917                return service.startScoUsingVirtualVoiceCall();
918            } catch (RemoteException e) {
919                Log.e(TAG, e.toString());
920            }
921        } else {
922            Log.w(TAG, "Proxy not attached to service");
923            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
924        }
925        return false;
926    }
927
928    /**
929     * Terminates an ongoing SCO connection and the associated virtual call.
930     *
931     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
932     * If this function returns true, this intent will be broadcasted with
933     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
934     *
935     * @return true if successful, false if one of the following case applies
936     *  - virtual voice call is not started or has ended
937     *  - binder is dead or Bluetooth is disabled or other error
938     * @hide
939     */
940    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
941    public boolean stopScoUsingVirtualVoiceCall() {
942        if (DBG) log("stopScoUsingVirtualVoiceCall()");
943        final IBluetoothHeadset service = mService;
944        if (service != null && isEnabled()) {
945            try {
946                return service.stopScoUsingVirtualVoiceCall();
947            } catch (RemoteException e) {
948                Log.e(TAG, e.toString());
949            }
950        } else {
951            Log.w(TAG, "Proxy not attached to service");
952            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
953        }
954        return false;
955    }
956
957    /**
958     * Notify Headset of phone state change.
959     * This is a backdoor for phone app to call BluetoothHeadset since
960     * there is currently not a good way to get precise call state change outside
961     * of phone app.
962     *
963     * @hide
964     */
965    public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
966            int type) {
967        final IBluetoothHeadset service = mService;
968        if (service != null && isEnabled()) {
969            try {
970                service.phoneStateChanged(numActive, numHeld, callState, number, type);
971            } catch (RemoteException e) {
972                Log.e(TAG, e.toString());
973            }
974        } else {
975            Log.w(TAG, "Proxy not attached to service");
976            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
977        }
978    }
979
980    /**
981     * Send Headset of CLCC response
982     *
983     * @hide
984     */
985    public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
986            String number, int type) {
987        final IBluetoothHeadset service = mService;
988        if (service != null && isEnabled()) {
989            try {
990                service.clccResponse(index, direction, status, mode, mpty, number, type);
991            } catch (RemoteException e) {
992                Log.e(TAG, e.toString());
993            }
994        } else {
995            Log.w(TAG, "Proxy not attached to service");
996            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
997        }
998    }
999
1000    /**
1001     * Sends a vendor-specific unsolicited result code to the headset.
1002     *
1003     * <p>The actual string to be sent is <code>command + ": " + arg</code>. For example, if {@code
1004     * command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg} is {@code "0"}, the
1005     * string <code>"+ANDROID: 0"</code> will be sent.
1006     *
1007     * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}.
1008     *
1009     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
1010     *
1011     * @param device Bluetooth headset.
1012     * @param command A vendor-specific command.
1013     * @param arg The argument that will be attached to the command.
1014     * @return {@code false} if there is no headset connected, or if the command is not an allowed
1015     * vendor-specific unsolicited result code, or on error. {@code true} otherwise.
1016     * @throws IllegalArgumentException if {@code command} is {@code null}.
1017     */
1018    public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
1019            String arg) {
1020        if (DBG) {
1021            log("sendVendorSpecificResultCode()");
1022        }
1023        if (command == null) {
1024            throw new IllegalArgumentException("command is null");
1025        }
1026        final IBluetoothHeadset service = mService;
1027        if (service != null && isEnabled() && isValidDevice(device)) {
1028            try {
1029                return service.sendVendorSpecificResultCode(device, command, arg);
1030            } catch (RemoteException e) {
1031                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1032            }
1033        }
1034        if (service == null) {
1035            Log.w(TAG, "Proxy not attached to service");
1036        }
1037        return false;
1038    }
1039
1040    /**
1041     * Select a connected device as active.
1042     *
1043     * The active device selection is per profile. An active device's
1044     * purpose is profile-specific. For example, in HFP and HSP profiles,
1045     * it is the device used for phone call audio. If a remote device is not
1046     * connected, it cannot be selected as active.
1047     *
1048     * <p> This API returns false in scenarios like the profile on the
1049     * device is not connected or Bluetooth is not turned on.
1050     * When this API returns true, it is guaranteed that the
1051     * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
1052     * with the active device.
1053     *
1054     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
1055     * permission.
1056     *
1057     * @param device Remote Bluetooth Device, could be null if phone call audio should not be
1058     * streamed to a headset
1059     * @return false on immediate error, true otherwise
1060     * @hide
1061     */
1062    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
1063    public boolean setActiveDevice(@Nullable BluetoothDevice device) {
1064        if (DBG) {
1065            Log.d(TAG, "setActiveDevice: " + device);
1066        }
1067        final IBluetoothHeadset service = mService;
1068        if (service != null && isEnabled() && (device == null || isValidDevice(device))) {
1069            try {
1070                return service.setActiveDevice(device);
1071            } catch (RemoteException e) {
1072                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1073            }
1074        }
1075        if (service == null) {
1076            Log.w(TAG, "Proxy not attached to service");
1077        }
1078        return false;
1079    }
1080
1081    /**
1082     * Get the connected device that is active.
1083     *
1084     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
1085     * permission.
1086     *
1087     * @return the connected device that is active or null if no device
1088     * is active.
1089     * @hide
1090     */
1091    @RequiresPermission(android.Manifest.permission.BLUETOOTH)
1092    public BluetoothDevice getActiveDevice() {
1093        if (VDBG) {
1094            Log.d(TAG, "getActiveDevice");
1095        }
1096        final IBluetoothHeadset service = mService;
1097        if (service != null && isEnabled()) {
1098            try {
1099                return service.getActiveDevice();
1100            } catch (RemoteException e) {
1101                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1102            }
1103        }
1104        if (service == null) {
1105            Log.w(TAG, "Proxy not attached to service");
1106        }
1107        return null;
1108    }
1109
1110    /**
1111     * Check if in-band ringing is currently enabled. In-band ringing could be disabled during an
1112     * active connection.
1113     *
1114     * @return true if in-band ringing is enabled, false if in-band ringing is disabled
1115     * @hide
1116     */
1117    @RequiresPermission(android.Manifest.permission.BLUETOOTH)
1118    public boolean isInbandRingingEnabled() {
1119        if (DBG) {
1120            log("isInbandRingingEnabled()");
1121        }
1122        final IBluetoothHeadset service = mService;
1123        if (service != null && isEnabled()) {
1124            try {
1125                return service.isInbandRingingEnabled();
1126            } catch (RemoteException e) {
1127                Log.e(TAG, Log.getStackTraceString(new Throwable()));
1128            }
1129        }
1130        if (service == null) {
1131            Log.w(TAG, "Proxy not attached to service");
1132        }
1133        return false;
1134    }
1135
1136    /**
1137     * Check if in-band ringing is supported for this platform.
1138     *
1139     * @return true if in-band ringing is supported, false if in-band ringing is not supported
1140     * @hide
1141     */
1142    public static boolean isInbandRingingSupported(Context context) {
1143        return context.getResources().getBoolean(
1144                com.android.internal.R.bool.config_bluetooth_hfp_inband_ringing_support);
1145    }
1146
1147    private final IBluetoothProfileServiceConnection mConnection =
1148            new IBluetoothProfileServiceConnection.Stub() {
1149        @Override
1150        public void onServiceConnected(ComponentName className, IBinder service) {
1151            if (DBG) Log.d(TAG, "Proxy object connected");
1152            mService = IBluetoothHeadset.Stub.asInterface(Binder.allowBlocking(service));
1153            mHandler.sendMessage(mHandler.obtainMessage(
1154                    MESSAGE_HEADSET_SERVICE_CONNECTED));
1155        }
1156
1157        @Override
1158        public void onServiceDisconnected(ComponentName className) {
1159            if (DBG) Log.d(TAG, "Proxy object disconnected");
1160            mService = null;
1161            mHandler.sendMessage(mHandler.obtainMessage(
1162                    MESSAGE_HEADSET_SERVICE_DISCONNECTED));
1163        }
1164    };
1165
1166    private boolean isEnabled() {
1167        return mAdapter.getState() == BluetoothAdapter.STATE_ON;
1168    }
1169
1170    private boolean isDisabled() {
1171        return mAdapter.getState() == BluetoothAdapter.STATE_OFF;
1172    }
1173
1174    private static boolean isValidDevice(BluetoothDevice device) {
1175        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
1176    }
1177
1178    private static void log(String msg) {
1179        Log.d(TAG, msg);
1180    }
1181
1182    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
1183        @Override
1184        public void handleMessage(Message msg) {
1185            switch (msg.what) {
1186                case MESSAGE_HEADSET_SERVICE_CONNECTED: {
1187                    if (mServiceListener != null) {
1188                        mServiceListener.onServiceConnected(BluetoothProfile.HEADSET,
1189                                BluetoothHeadset.this);
1190                    }
1191                    break;
1192                }
1193                case MESSAGE_HEADSET_SERVICE_DISCONNECTED: {
1194                    if (mServiceListener != null) {
1195                        mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
1196                    }
1197                    break;
1198                }
1199            }
1200        }
1201    };
1202}
1203