BluetoothHeadset.java revision e8b98925d08f720c4d22b626d0650de536840a9a
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.annotation.SdkConstant;
20import android.annotation.SdkConstant.SdkConstantType;
21import android.content.ComponentName;
22import android.content.Context;
23import android.os.Handler;
24import android.os.IBinder;
25import android.os.Looper;
26import android.os.Message;
27import android.os.RemoteException;
28import android.util.Log;
29
30import java.util.ArrayList;
31import java.util.List;
32
33/**
34 * Public API for controlling the Bluetooth Headset Service. This includes both
35 * Bluetooth Headset and Handsfree (v1.5) profiles.
36 *
37 * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
38 * Service via IPC.
39 *
40 * <p> Use {@link BluetoothAdapter#getProfileProxy} to get
41 * the BluetoothHeadset proxy object. Use
42 * {@link BluetoothAdapter#closeProfileProxy} to close the service connection.
43 *
44 * <p> Android only supports one connected Bluetooth Headset at a time.
45 * Each method is protected with its appropriate permission.
46 */
47public final class BluetoothHeadset implements BluetoothProfile {
48    private static final String TAG = "BluetoothHeadset";
49    private static final boolean DBG = true;
50    private static final boolean VDBG = false;
51
52    /**
53     * Intent used to broadcast the change in connection state of the Headset
54     * profile.
55     *
56     * <p>This intent will have 3 extras:
57     * <ul>
58     *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
59     *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
60     *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
61     * </ul>
62     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
63     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
64     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
65     *
66     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
67     * receive.
68     */
69    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
70    public static final String ACTION_CONNECTION_STATE_CHANGED =
71        "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
72
73    /**
74     * Intent used to broadcast the change in the Audio Connection state of the
75     * A2DP profile.
76     *
77     * <p>This intent will have 3 extras:
78     * <ul>
79     *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
80     *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
81     *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
82     * </ul>
83     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
84     * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED},
85     *
86     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
87     * to receive.
88     */
89    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
90    public static final String ACTION_AUDIO_STATE_CHANGED =
91        "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
92
93
94    /**
95     * Intent used to broadcast that the headset has posted a
96     * vendor-specific event.
97     *
98     * <p>This intent will have 4 extras and 1 category.
99     * <ul>
100     *  <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device
101     *       </li>
102     *  <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor
103     *       specific command </li>
104     *  <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT
105     *       command type which can be one of  {@link #AT_CMD_TYPE_READ},
106     *       {@link #AT_CMD_TYPE_TEST}, or {@link #AT_CMD_TYPE_SET},
107     *       {@link #AT_CMD_TYPE_BASIC},{@link #AT_CMD_TYPE_ACTION}. </li>
108     *  <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command
109     *       arguments. </li>
110     * </ul>
111     *
112     *<p> The category is the Company ID of the vendor defining the
113     * vendor-specific command. {@link BluetoothAssignedNumbers}
114     *
115     * For example, for Plantronics specific events
116     * Category will be {@link #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55
117     *
118     * <p> For example, an AT+XEVENT=foo,3 will get translated into
119     * <ul>
120     *   <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT </li>
121     *   <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li>
122     *   <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li>
123     * </ul>
124     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
125     * to receive.
126     */
127    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
128    public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT =
129            "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
130
131    /**
132     * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
133     * intents that contains the name of the vendor-specific command.
134     */
135    public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD =
136            "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD";
137
138    /**
139     * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
140     * intents that contains the AT command type of the vendor-specific command.
141     */
142    public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE =
143            "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE";
144
145    /**
146     * AT command type READ used with
147     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
148     * For example, AT+VGM?. There are no arguments for this command type.
149     */
150    public static final int AT_CMD_TYPE_READ = 0;
151
152    /**
153     * AT command type TEST used with
154     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
155     * For example, AT+VGM=?. There are no arguments for this command type.
156     */
157    public static final int AT_CMD_TYPE_TEST = 1;
158
159    /**
160     * AT command type SET used with
161     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
162     * For example, AT+VGM=<args>.
163     */
164    public static final int AT_CMD_TYPE_SET = 2;
165
166    /**
167     * AT command type BASIC used with
168     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
169     * For example, ATD. Single character commands and everything following the
170     * character are arguments.
171     */
172    public static final int AT_CMD_TYPE_BASIC = 3;
173
174    /**
175     * AT command type ACTION used with
176     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
177     * For example, AT+CHUP. There are no arguments for action commands.
178     */
179    public static final int AT_CMD_TYPE_ACTION = 4;
180
181    /**
182     * A Parcelable String array extra field in
183     * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains
184     * the arguments to the vendor-specific command.
185     */
186    public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS =
187            "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS";
188
189    /**
190     * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
191     * for the companyId
192     */
193    public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY  =
194            "android.bluetooth.headset.intent.category.companyid";
195
196    /**
197     * A vendor-specific command for unsolicited result code.
198     */
199    public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID";
200
201    /**
202     * Headset state when SCO audio is not connected.
203     * This state can be one of
204     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
205     * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
206     */
207    public static final int STATE_AUDIO_DISCONNECTED = 10;
208
209    /**
210     * Headset state when SCO audio is connecting.
211     * This state can be one of
212     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
213     * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
214     */
215    public static final int STATE_AUDIO_CONNECTING = 11;
216
217    /**
218     * Headset state when SCO audio is connected.
219     * This state can be one of
220     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
221     * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
222     */
223    public static final int STATE_AUDIO_CONNECTED = 12;
224
225    private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100;
226    private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101;
227
228    private Context mContext;
229    private ServiceListener mServiceListener;
230    private IBluetoothHeadset mService;
231    private BluetoothAdapter mAdapter;
232    private boolean mIsClosed;
233
234    final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
235            new IBluetoothStateChangeCallback.Stub() {
236                public void onBluetoothStateChange(boolean up) {
237                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
238                    if (!up) {
239                        if (VDBG) Log.d(TAG,"Unbinding service...");
240                        doUnbind();
241                    } else {
242                        synchronized (mConnection) {
243                            try {
244                                if (mService == null) {
245                                    if (VDBG) Log.d(TAG,"Binding service...");
246                                    doBind();
247                                }
248                            } catch (Exception re) {
249                                Log.e(TAG,"",re);
250                            }
251                        }
252                    }
253                }
254        };
255
256    /**
257     * Create a BluetoothHeadset proxy object.
258     */
259    /*package*/ BluetoothHeadset(Context context, ServiceListener l) {
260        mContext = context;
261        mServiceListener = l;
262        mAdapter = BluetoothAdapter.getDefaultAdapter();
263        mIsClosed = false;
264
265        IBluetoothManager mgr = mAdapter.getBluetoothManager();
266        if (mgr != null) {
267            try {
268                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
269            } catch (RemoteException e) {
270                Log.e(TAG,"",e);
271            }
272        }
273
274        doBind();
275    }
276
277    boolean doBind() {
278        try {
279            return mAdapter.getBluetoothManager().bindBluetoothProfileService(
280                    BluetoothProfile.HEADSET, mConnection);
281        } catch (RemoteException e) {
282            Log.e(TAG, "Unable to bind HeadsetService", e);
283        }
284        return false;
285    }
286
287    void doUnbind() {
288        synchronized (mConnection) {
289            if (mService != null) {
290                try {
291                    mAdapter.getBluetoothManager().unbindBluetoothProfileService(
292                            BluetoothProfile.HEADSET, mConnection);
293                } catch (RemoteException e) {
294                    Log.e(TAG,"Unable to unbind HeadsetService", e);
295                }
296            }
297        }
298    }
299
300    /**
301     * Close the connection to the backing service.
302     * Other public functions of BluetoothHeadset will return default error
303     * results once close() has been called. Multiple invocations of close()
304     * are ok.
305     */
306    /*package*/ void close() {
307        if (VDBG) log("close()");
308
309        IBluetoothManager mgr = mAdapter.getBluetoothManager();
310        if (mgr != null) {
311            try {
312                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
313            } catch (Exception e) {
314                Log.e(TAG,"",e);
315            }
316        }
317        mIsClosed = true;
318        doUnbind();
319    }
320
321    /**
322     * Initiate connection to a profile of the remote bluetooth device.
323     *
324     * <p> Currently, the system supports only 1 connection to the
325     * headset/handsfree profile. The API will automatically disconnect connected
326     * devices before connecting.
327     *
328     * <p> This API returns false in scenarios like the profile on the
329     * device is already connected or Bluetooth is not turned on.
330     * When this API returns true, it is guaranteed that
331     * connection state intent for the profile will be broadcasted with
332     * the state. Users can get the connection state of the profile
333     * from this intent.
334     *
335     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
336     * permission.
337     *
338     * @param device Remote Bluetooth Device
339     * @return false on immediate error,
340     *               true otherwise
341     * @hide
342     */
343    public boolean connect(BluetoothDevice device) {
344        if (DBG) log("connect(" + device + ")");
345        if (mService != null && isEnabled() &&
346            isValidDevice(device)) {
347            try {
348                return mService.connect(device);
349            } catch (RemoteException e) {
350                Log.e(TAG, Log.getStackTraceString(new Throwable()));
351                return false;
352            }
353        }
354        if (mService == null) Log.w(TAG, "Proxy not attached to service");
355        return false;
356    }
357
358    /**
359     * Initiate disconnection from a profile
360     *
361     * <p> This API will return false in scenarios like the profile on the
362     * Bluetooth device is not in connected state etc. When this API returns,
363     * true, it is guaranteed that the connection state change
364     * intent will be broadcasted with the state. Users can get the
365     * disconnection state of the profile from this intent.
366     *
367     * <p> If the disconnection is initiated by a remote device, the state
368     * will transition from {@link #STATE_CONNECTED} to
369     * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
370     * host (local) device the state will transition from
371     * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
372     * state {@link #STATE_DISCONNECTED}. The transition to
373     * {@link #STATE_DISCONNECTING} can be used to distinguish between the
374     * two scenarios.
375     *
376     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
377     * permission.
378     *
379     * @param device Remote Bluetooth Device
380     * @return false on immediate error,
381     *               true otherwise
382     * @hide
383     */
384    public boolean disconnect(BluetoothDevice device) {
385        if (DBG) log("disconnect(" + device + ")");
386        if (mService != null && isEnabled() &&
387            isValidDevice(device)) {
388            try {
389                return mService.disconnect(device);
390            } catch (RemoteException e) {
391              Log.e(TAG, Log.getStackTraceString(new Throwable()));
392              return false;
393            }
394        }
395        if (mService == null) Log.w(TAG, "Proxy not attached to service");
396        return false;
397    }
398
399    /**
400     * {@inheritDoc}
401     */
402    public List<BluetoothDevice> getConnectedDevices() {
403        if (VDBG) log("getConnectedDevices()");
404        if (mService != null && isEnabled()) {
405            try {
406                return mService.getConnectedDevices();
407            } catch (RemoteException e) {
408                Log.e(TAG, Log.getStackTraceString(new Throwable()));
409                return new ArrayList<BluetoothDevice>();
410            }
411        }
412        if (mService == null) Log.w(TAG, "Proxy not attached to service");
413        return new ArrayList<BluetoothDevice>();
414    }
415
416    /**
417     * {@inheritDoc}
418     */
419    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
420        if (VDBG) log("getDevicesMatchingStates()");
421        if (mService != null && isEnabled()) {
422            try {
423                return mService.getDevicesMatchingConnectionStates(states);
424            } catch (RemoteException e) {
425                Log.e(TAG, Log.getStackTraceString(new Throwable()));
426                return new ArrayList<BluetoothDevice>();
427            }
428        }
429        if (mService == null) Log.w(TAG, "Proxy not attached to service");
430        return new ArrayList<BluetoothDevice>();
431    }
432
433    /**
434     * {@inheritDoc}
435     */
436    public int getConnectionState(BluetoothDevice device) {
437        if (VDBG) log("getConnectionState(" + device + ")");
438        if (mService != null && isEnabled() &&
439            isValidDevice(device)) {
440            try {
441                return mService.getConnectionState(device);
442            } catch (RemoteException e) {
443                Log.e(TAG, Log.getStackTraceString(new Throwable()));
444                return BluetoothProfile.STATE_DISCONNECTED;
445            }
446        }
447        if (mService == null) Log.w(TAG, "Proxy not attached to service");
448        return BluetoothProfile.STATE_DISCONNECTED;
449    }
450
451    /**
452     * Set priority of the profile
453     *
454     * <p> The device should already be paired.
455     *  Priority can be one of {@link #PRIORITY_ON} or
456     * {@link #PRIORITY_OFF},
457     *
458     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
459     * permission.
460     *
461     * @param device Paired bluetooth device
462     * @param priority
463     * @return true if priority is set, false on error
464     * @hide
465     */
466    public boolean setPriority(BluetoothDevice device, int priority) {
467        if (DBG) log("setPriority(" + device + ", " + priority + ")");
468        if (mService != null && isEnabled() &&
469            isValidDevice(device)) {
470            if (priority != BluetoothProfile.PRIORITY_OFF &&
471                priority != BluetoothProfile.PRIORITY_ON) {
472              return false;
473            }
474            try {
475                return mService.setPriority(device, priority);
476            } catch (RemoteException e) {
477                Log.e(TAG, Log.getStackTraceString(new Throwable()));
478                return false;
479            }
480        }
481        if (mService == null) Log.w(TAG, "Proxy not attached to service");
482        return false;
483    }
484
485    /**
486     * Get the priority of the profile.
487     *
488     * <p> The priority can be any of:
489     * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
490     * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
491     *
492     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
493     *
494     * @param device Bluetooth device
495     * @return priority of the device
496     * @hide
497     */
498    public int getPriority(BluetoothDevice device) {
499        if (VDBG) log("getPriority(" + device + ")");
500        if (mService != null && isEnabled() &&
501            isValidDevice(device)) {
502            try {
503                return mService.getPriority(device);
504            } catch (RemoteException e) {
505                Log.e(TAG, Log.getStackTraceString(new Throwable()));
506                return PRIORITY_OFF;
507            }
508        }
509        if (mService == null) Log.w(TAG, "Proxy not attached to service");
510        return PRIORITY_OFF;
511    }
512
513    /**
514     * Start Bluetooth voice recognition. This methods sends the voice
515     * recognition AT command to the headset and establishes the
516     * audio connection.
517     *
518     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
519     * If this function returns true, this intent will be broadcasted with
520     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
521     *
522     * <p> {@link #EXTRA_STATE} will transition from
523     * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
524     * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
525     * in case of failure to establish the audio connection.
526     *
527     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
528     *
529     * @param device Bluetooth headset
530     * @return false if there is no headset connected of if the
531     *               connected headset doesn't support voice recognition
532     *               or on error, true otherwise
533     */
534    public boolean startVoiceRecognition(BluetoothDevice device) {
535        if (DBG) log("startVoiceRecognition()");
536        if (mService != null && isEnabled() &&
537            isValidDevice(device)) {
538            try {
539                return mService.startVoiceRecognition(device);
540            } catch (RemoteException e) {
541                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
542            }
543        }
544        if (mService == null) Log.w(TAG, "Proxy not attached to service");
545        return false;
546    }
547
548    /**
549     * Stop Bluetooth Voice Recognition mode, and shut down the
550     * Bluetooth audio path.
551     *
552     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
553     *
554     * @param device Bluetooth headset
555     * @return false if there is no headset connected
556     *               or on error, true otherwise
557     */
558    public boolean stopVoiceRecognition(BluetoothDevice device) {
559        if (DBG) log("stopVoiceRecognition()");
560        if (mService != null && isEnabled() &&
561            isValidDevice(device)) {
562            try {
563                return mService.stopVoiceRecognition(device);
564            } catch (RemoteException e) {
565                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
566            }
567        }
568        if (mService == null) Log.w(TAG, "Proxy not attached to service");
569        return false;
570    }
571
572    /**
573     * Check if Bluetooth SCO audio is connected.
574     *
575     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
576     *
577     * @param device Bluetooth headset
578     * @return true if SCO is connected,
579     *         false otherwise or on error
580     */
581    public boolean isAudioConnected(BluetoothDevice device) {
582        if (VDBG) log("isAudioConnected()");
583        if (mService != null && isEnabled() &&
584            isValidDevice(device)) {
585            try {
586              return mService.isAudioConnected(device);
587            } catch (RemoteException e) {
588              Log.e(TAG,  Log.getStackTraceString(new Throwable()));
589            }
590        }
591        if (mService == null) Log.w(TAG, "Proxy not attached to service");
592        return false;
593    }
594
595    /**
596     * Get battery usage hint for Bluetooth Headset service.
597     * This is a monotonically increasing integer. Wraps to 0 at
598     * Integer.MAX_INT, and at boot.
599     * Current implementation returns the number of AT commands handled since
600     * boot. This is a good indicator for spammy headset/handsfree units that
601     * can keep the device awake by polling for cellular status updates. As a
602     * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
603     *
604     * @param device the bluetooth headset.
605     * @return monotonically increasing battery usage hint, or a negative error
606     *         code on error
607     * @hide
608     */
609    public int getBatteryUsageHint(BluetoothDevice device) {
610        if (VDBG) log("getBatteryUsageHint()");
611        if (mService != null && isEnabled() &&
612            isValidDevice(device)) {
613            try {
614                return mService.getBatteryUsageHint(device);
615            } catch (RemoteException e) {
616                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
617            }
618        }
619        if (mService == null) Log.w(TAG, "Proxy not attached to service");
620        return -1;
621    }
622
623    /**
624     * Indicates if current platform supports voice dialing over bluetooth SCO.
625     *
626     * @return true if voice dialing over bluetooth is supported, false otherwise.
627     * @hide
628     */
629    public static boolean isBluetoothVoiceDialingEnabled(Context context) {
630        return context.getResources().getBoolean(
631                com.android.internal.R.bool.config_bluetooth_sco_off_call);
632    }
633
634    /**
635     * Accept the incoming connection.
636     * Note: This is an internal function and shouldn't be exposed
637     *
638     * @hide
639     */
640    public boolean acceptIncomingConnect(BluetoothDevice device) {
641        if (DBG) log("acceptIncomingConnect");
642        if (mService != null && isEnabled()) {
643            try {
644                return mService.acceptIncomingConnect(device);
645            } catch (RemoteException e) {Log.e(TAG, e.toString());}
646        } else {
647            Log.w(TAG, "Proxy not attached to service");
648            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
649        }
650        return false;
651    }
652
653    /**
654     * Reject the incoming connection.
655     * @hide
656     */
657    public boolean rejectIncomingConnect(BluetoothDevice device) {
658        if (DBG) log("rejectIncomingConnect");
659        if (mService != null) {
660            try {
661                return mService.rejectIncomingConnect(device);
662            } catch (RemoteException e) {Log.e(TAG, e.toString());}
663        } else {
664            Log.w(TAG, "Proxy not attached to service");
665            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
666        }
667        return false;
668    }
669
670    /**
671     * Get the current audio state of the Headset.
672     * Note: This is an internal function and shouldn't be exposed
673     *
674     * @hide
675     */
676    public int getAudioState(BluetoothDevice device) {
677        if (VDBG) log("getAudioState");
678        if (mService != null && !isDisabled()) {
679            try {
680                return mService.getAudioState(device);
681            } catch (RemoteException e) {Log.e(TAG, e.toString());}
682        } else {
683            Log.w(TAG, "Proxy not attached to service");
684            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
685        }
686        return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
687    }
688
689    /**
690     * Check if Bluetooth SCO audio is connected.
691     *
692     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
693     *
694     * @return true if SCO is connected,
695     *         false otherwise or on error
696     * @hide
697     */
698    public boolean isAudioOn() {
699        if (VDBG) log("isAudioOn()");
700        if (mService != null && isEnabled()) {
701            try {
702              return mService.isAudioOn();
703            } catch (RemoteException e) {
704              Log.e(TAG,  Log.getStackTraceString(new Throwable()));
705            }
706        }
707        if (mService == null) Log.w(TAG, "Proxy not attached to service");
708        return false;
709
710    }
711
712    /**
713     * Initiates a connection of headset audio.
714     * It setup SCO channel with remote connected headset device.
715     *
716     * @return true if successful
717     *         false if there was some error such as
718     *               there is no connected headset
719     * @hide
720     */
721    public boolean connectAudio() {
722        if (mService != null && isEnabled()) {
723            try {
724                return mService.connectAudio();
725            } catch (RemoteException e) {
726                Log.e(TAG, e.toString());
727            }
728        } else {
729            Log.w(TAG, "Proxy not attached to service");
730            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
731        }
732        return false;
733    }
734
735    /**
736     * Initiates a disconnection of headset audio.
737     * It tears down the SCO channel from remote headset device.
738     *
739     * @return true if successful
740     *         false if there was some error such as
741     *               there is no connected SCO channel
742     * @hide
743     */
744    public boolean disconnectAudio() {
745        if (mService != null && isEnabled()) {
746            try {
747                return mService.disconnectAudio();
748            } catch (RemoteException e) {
749                Log.e(TAG, e.toString());
750            }
751        } else {
752            Log.w(TAG, "Proxy not attached to service");
753            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
754        }
755        return false;
756    }
757
758    /**
759     * Initiates a SCO channel connection with the headset (if connected).
760     * Also initiates a virtual voice call for Handsfree devices as many devices
761     * do not accept SCO audio without a call.
762     * This API allows the handsfree device to be used for routing non-cellular
763     * call audio.
764     *
765     * @param device Remote Bluetooth Device
766     * @return true if successful, false if there was some error.
767     * @hide
768     */
769    public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
770        if (DBG) log("startScoUsingVirtualVoiceCall()");
771        if (mService != null && isEnabled() && isValidDevice(device)) {
772            try {
773                return mService.startScoUsingVirtualVoiceCall(device);
774            } catch (RemoteException e) {
775                Log.e(TAG, e.toString());
776            }
777        } else {
778            Log.w(TAG, "Proxy not attached to service");
779            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
780        }
781        return false;
782    }
783
784    /**
785     * Terminates an ongoing SCO connection and the associated virtual
786     * call.
787     *
788     * @param device Remote Bluetooth Device
789     * @return true if successful, false if there was some error.
790     * @hide
791     */
792    public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
793        if (DBG) log("stopScoUsingVirtualVoiceCall()");
794        if (mService != null && isEnabled() && isValidDevice(device)) {
795            try {
796                return mService.stopScoUsingVirtualVoiceCall(device);
797            } catch (RemoteException e) {
798                Log.e(TAG, e.toString());
799            }
800        } else {
801            Log.w(TAG, "Proxy not attached to service");
802            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
803        }
804        return false;
805    }
806
807    /**
808     * Notify Headset of phone state change.
809     * This is a backdoor for phone app to call BluetoothHeadset since
810     * there is currently not a good way to get precise call state change outside
811     * of phone app.
812     *
813     * @hide
814     */
815    public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
816                                  int type) {
817        if (mService != null && isEnabled()) {
818            try {
819                mService.phoneStateChanged(numActive, numHeld, callState, number, type);
820            } catch (RemoteException e) {
821                Log.e(TAG, e.toString());
822            }
823        } else {
824            Log.w(TAG, "Proxy not attached to service");
825            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
826        }
827    }
828
829    /**
830     * Send Headset of CLCC response
831     *
832     * @hide
833     */
834    public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
835                             String number, int type) {
836        if (mService != null && isEnabled()) {
837            try {
838                mService.clccResponse(index, direction, status, mode, mpty, number, type);
839            } catch (RemoteException e) {
840                Log.e(TAG, e.toString());
841            }
842        } else {
843            Log.w(TAG, "Proxy not attached to service");
844            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
845        }
846    }
847
848    /**
849     * Sends a vendor-specific unsolicited result code to the headset.
850     *
851     * <p>The actual string to be sent is <code>command + ": " + arg</code>.
852     * For example, if {@code command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg}
853     * is {@code "0"}, the string <code>"+ANDROID: 0"</code> will be sent.
854     *
855     * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}.
856     *
857     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
858     *
859     * @param device Bluetooth headset.
860     * @param command A vendor-specific command.
861     * @param arg The argument that will be attached to the command.
862     * @return {@code false} if there is no headset connected, or if the command is not an allowed
863     *         vendor-specific unsolicited result code, or on error. {@code true} otherwise.
864     * @throws IllegalArgumentException if {@code command} is {@code null}.
865     */
866    public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
867            String arg) {
868        if (DBG) {
869            log("sendVendorSpecificResultCode()");
870        }
871        if (command == null) {
872            throw new IllegalArgumentException("command is null");
873        }
874        if (mService != null && isEnabled() &&
875                isValidDevice(device)) {
876            try {
877                return mService.sendVendorSpecificResultCode(device, command, arg);
878            } catch (RemoteException e) {
879                Log.e(TAG, Log.getStackTraceString(new Throwable()));
880            }
881        }
882        if (mService == null) {
883            Log.w(TAG, "Proxy not attached to service");
884        }
885        return false;
886    }
887
888    /**
889     * enable WBS codec setting.
890     *
891     * @return true if successful
892     *         false if there was some error such as
893     *               there is no connected headset
894     * @hide
895     */
896    public boolean enableWBS() {
897        if (mService != null && isEnabled()) {
898            try {
899                return mService.enableWBS();
900            } catch (RemoteException e) {
901                Log.e(TAG, e.toString());
902            }
903        } else {
904            Log.w(TAG, "Proxy not attached to service");
905            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
906        }
907        return false;
908    }
909
910    /**
911     * disable WBS codec settting. It set NBS codec.
912     *
913     * @return true if successful
914     *         false if there was some error such as
915     *               there is no connected headset
916     * @hide
917     */
918    public boolean disableWBS() {
919        if (mService != null && isEnabled()) {
920            try {
921                return mService.disableWBS();
922            } catch (RemoteException e) {
923                Log.e(TAG, e.toString());
924            }
925        } else {
926            Log.w(TAG, "Proxy not attached to service");
927            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
928        }
929        return false;
930    }
931
932    private final IBluetoothProfileServiceConnection mConnection
933            = new IBluetoothProfileServiceConnection.Stub()  {
934        @Override
935        public void onServiceConnected(ComponentName className, IBinder service) {
936            if (DBG) Log.d(TAG, "Proxy object connected");
937            mService = IBluetoothHeadset.Stub.asInterface(service);
938            mHandler.sendMessage(mHandler.obtainMessage(
939                    MESSAGE_HEADSET_SERVICE_CONNECTED));
940        }
941        @Override
942        public void onServiceDisconnected(ComponentName className) {
943            if (DBG) Log.d(TAG, "Proxy object disconnected");
944            mService = null;
945            mHandler.sendMessage(mHandler.obtainMessage(
946                    MESSAGE_HEADSET_SERVICE_DISCONNECTED));
947        }
948    };
949
950    private boolean isEnabled() {
951       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
952       return false;
953    }
954
955    private boolean isDisabled() {
956       if (mAdapter.getState() == BluetoothAdapter.STATE_OFF) return true;
957       return false;
958    }
959
960    private boolean isValidDevice(BluetoothDevice device) {
961       if (device == null) return false;
962
963       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
964       return false;
965    }
966
967    private static void log(String msg) {
968        Log.d(TAG, msg);
969    }
970
971    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
972        @Override
973        public void handleMessage(Message msg) {
974            switch (msg.what) {
975                case MESSAGE_HEADSET_SERVICE_CONNECTED: {
976                    if (mServiceListener != null) {
977                        mServiceListener.onServiceConnected(BluetoothProfile.HEADSET,
978                                BluetoothHeadset.this);
979                    }
980                    break;
981                }
982                case MESSAGE_HEADSET_SERVICE_DISCONNECTED: {
983                    if (mServiceListener != null) {
984                        mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
985                    }
986                    if (mIsClosed){
987                        mServiceListener = null;
988                    }
989                    break;
990                }
991            }
992        }
993    };
994}
995