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
233    final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
234            new IBluetoothStateChangeCallback.Stub() {
235                public void onBluetoothStateChange(boolean up) {
236                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
237                    if (!up) {
238                        if (VDBG) Log.d(TAG,"Unbinding service...");
239                        doUnbind();
240                    } else {
241                        synchronized (mConnection) {
242                            try {
243                                if (mService == null) {
244                                    if (VDBG) Log.d(TAG,"Binding service...");
245                                    doBind();
246                                }
247                            } catch (Exception re) {
248                                Log.e(TAG,"",re);
249                            }
250                        }
251                    }
252                }
253        };
254
255    /**
256     * Create a BluetoothHeadset proxy object.
257     */
258    /*package*/ BluetoothHeadset(Context context, ServiceListener l) {
259        mContext = context;
260        mServiceListener = l;
261        mAdapter = BluetoothAdapter.getDefaultAdapter();
262
263        IBluetoothManager mgr = mAdapter.getBluetoothManager();
264        if (mgr != null) {
265            try {
266                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
267            } catch (RemoteException e) {
268                Log.e(TAG,"",e);
269            }
270        }
271
272        doBind();
273    }
274
275    boolean doBind() {
276        try {
277            return mAdapter.getBluetoothManager().bindBluetoothProfileService(
278                    BluetoothProfile.HEADSET, mConnection);
279        } catch (RemoteException e) {
280            Log.e(TAG, "Unable to bind HeadsetService", e);
281        }
282        return false;
283    }
284
285    void doUnbind() {
286        synchronized (mConnection) {
287            if (mService != null) {
288                try {
289                    mAdapter.getBluetoothManager().unbindBluetoothProfileService(
290                            BluetoothProfile.HEADSET, mConnection);
291                } catch (RemoteException e) {
292                    Log.e(TAG,"Unable to unbind HeadsetService", e);
293                }
294            }
295        }
296    }
297
298    /**
299     * Close the connection to the backing service.
300     * Other public functions of BluetoothHeadset will return default error
301     * results once close() has been called. Multiple invocations of close()
302     * are ok.
303     */
304    /*package*/ void close() {
305        if (VDBG) log("close()");
306
307        IBluetoothManager mgr = mAdapter.getBluetoothManager();
308        if (mgr != null) {
309            try {
310                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
311            } catch (Exception e) {
312                Log.e(TAG,"",e);
313            }
314        }
315        mServiceListener = null;
316        doUnbind();
317    }
318
319    /**
320     * Initiate connection to a profile of the remote bluetooth device.
321     *
322     * <p> Currently, the system supports only 1 connection to the
323     * headset/handsfree profile. The API will automatically disconnect connected
324     * devices before connecting.
325     *
326     * <p> This API returns false in scenarios like the profile on the
327     * device is already connected or Bluetooth is not turned on.
328     * When this API returns true, it is guaranteed that
329     * connection state intent for the profile will be broadcasted with
330     * the state. Users can get the connection state of the profile
331     * from this intent.
332     *
333     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
334     * permission.
335     *
336     * @param device Remote Bluetooth Device
337     * @return false on immediate error,
338     *               true otherwise
339     * @hide
340     */
341    public boolean connect(BluetoothDevice device) {
342        if (DBG) log("connect(" + device + ")");
343        if (mService != null && isEnabled() &&
344            isValidDevice(device)) {
345            try {
346                return mService.connect(device);
347            } catch (RemoteException e) {
348                Log.e(TAG, Log.getStackTraceString(new Throwable()));
349                return false;
350            }
351        }
352        if (mService == null) Log.w(TAG, "Proxy not attached to service");
353        return false;
354    }
355
356    /**
357     * Initiate disconnection from a profile
358     *
359     * <p> This API will return false in scenarios like the profile on the
360     * Bluetooth device is not in connected state etc. When this API returns,
361     * true, it is guaranteed that the connection state change
362     * intent will be broadcasted with the state. Users can get the
363     * disconnection state of the profile from this intent.
364     *
365     * <p> If the disconnection is initiated by a remote device, the state
366     * will transition from {@link #STATE_CONNECTED} to
367     * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
368     * host (local) device the state will transition from
369     * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
370     * state {@link #STATE_DISCONNECTED}. The transition to
371     * {@link #STATE_DISCONNECTING} can be used to distinguish between the
372     * two scenarios.
373     *
374     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
375     * permission.
376     *
377     * @param device Remote Bluetooth Device
378     * @return false on immediate error,
379     *               true otherwise
380     * @hide
381     */
382    public boolean disconnect(BluetoothDevice device) {
383        if (DBG) log("disconnect(" + device + ")");
384        if (mService != null && isEnabled() &&
385            isValidDevice(device)) {
386            try {
387                return mService.disconnect(device);
388            } catch (RemoteException e) {
389              Log.e(TAG, Log.getStackTraceString(new Throwable()));
390              return false;
391            }
392        }
393        if (mService == null) Log.w(TAG, "Proxy not attached to service");
394        return false;
395    }
396
397    /**
398     * {@inheritDoc}
399     */
400    public List<BluetoothDevice> getConnectedDevices() {
401        if (VDBG) log("getConnectedDevices()");
402        if (mService != null && isEnabled()) {
403            try {
404                return mService.getConnectedDevices();
405            } catch (RemoteException e) {
406                Log.e(TAG, Log.getStackTraceString(new Throwable()));
407                return new ArrayList<BluetoothDevice>();
408            }
409        }
410        if (mService == null) Log.w(TAG, "Proxy not attached to service");
411        return new ArrayList<BluetoothDevice>();
412    }
413
414    /**
415     * {@inheritDoc}
416     */
417    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
418        if (VDBG) log("getDevicesMatchingStates()");
419        if (mService != null && isEnabled()) {
420            try {
421                return mService.getDevicesMatchingConnectionStates(states);
422            } catch (RemoteException e) {
423                Log.e(TAG, Log.getStackTraceString(new Throwable()));
424                return new ArrayList<BluetoothDevice>();
425            }
426        }
427        if (mService == null) Log.w(TAG, "Proxy not attached to service");
428        return new ArrayList<BluetoothDevice>();
429    }
430
431    /**
432     * {@inheritDoc}
433     */
434    public int getConnectionState(BluetoothDevice device) {
435        if (VDBG) log("getConnectionState(" + device + ")");
436        if (mService != null && isEnabled() &&
437            isValidDevice(device)) {
438            try {
439                return mService.getConnectionState(device);
440            } catch (RemoteException e) {
441                Log.e(TAG, Log.getStackTraceString(new Throwable()));
442                return BluetoothProfile.STATE_DISCONNECTED;
443            }
444        }
445        if (mService == null) Log.w(TAG, "Proxy not attached to service");
446        return BluetoothProfile.STATE_DISCONNECTED;
447    }
448
449    /**
450     * Set priority of the profile
451     *
452     * <p> The device should already be paired.
453     *  Priority can be one of {@link #PRIORITY_ON} or
454     * {@link #PRIORITY_OFF},
455     *
456     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
457     * permission.
458     *
459     * @param device Paired bluetooth device
460     * @param priority
461     * @return true if priority is set, false on error
462     * @hide
463     */
464    public boolean setPriority(BluetoothDevice device, int priority) {
465        if (DBG) log("setPriority(" + device + ", " + priority + ")");
466        if (mService != null && isEnabled() &&
467            isValidDevice(device)) {
468            if (priority != BluetoothProfile.PRIORITY_OFF &&
469                priority != BluetoothProfile.PRIORITY_ON) {
470              return false;
471            }
472            try {
473                return mService.setPriority(device, priority);
474            } catch (RemoteException e) {
475                Log.e(TAG, Log.getStackTraceString(new Throwable()));
476                return false;
477            }
478        }
479        if (mService == null) Log.w(TAG, "Proxy not attached to service");
480        return false;
481    }
482
483    /**
484     * Get the priority of the profile.
485     *
486     * <p> The priority can be any of:
487     * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
488     * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
489     *
490     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
491     *
492     * @param device Bluetooth device
493     * @return priority of the device
494     * @hide
495     */
496    public int getPriority(BluetoothDevice device) {
497        if (VDBG) log("getPriority(" + device + ")");
498        if (mService != null && isEnabled() &&
499            isValidDevice(device)) {
500            try {
501                return mService.getPriority(device);
502            } catch (RemoteException e) {
503                Log.e(TAG, Log.getStackTraceString(new Throwable()));
504                return PRIORITY_OFF;
505            }
506        }
507        if (mService == null) Log.w(TAG, "Proxy not attached to service");
508        return PRIORITY_OFF;
509    }
510
511    /**
512     * Start Bluetooth voice recognition. This methods sends the voice
513     * recognition AT command to the headset and establishes the
514     * audio connection.
515     *
516     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
517     * If this function returns true, this intent will be broadcasted with
518     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
519     *
520     * <p> {@link #EXTRA_STATE} will transition from
521     * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
522     * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
523     * in case of failure to establish the audio connection.
524     *
525     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
526     *
527     * @param device Bluetooth headset
528     * @return false if there is no headset connected of if the
529     *               connected headset doesn't support voice recognition
530     *               or on error, true otherwise
531     */
532    public boolean startVoiceRecognition(BluetoothDevice device) {
533        if (DBG) log("startVoiceRecognition()");
534        if (mService != null && isEnabled() &&
535            isValidDevice(device)) {
536            try {
537                return mService.startVoiceRecognition(device);
538            } catch (RemoteException e) {
539                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
540            }
541        }
542        if (mService == null) Log.w(TAG, "Proxy not attached to service");
543        return false;
544    }
545
546    /**
547     * Stop Bluetooth Voice Recognition mode, and shut down the
548     * Bluetooth audio path.
549     *
550     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
551     *
552     * @param device Bluetooth headset
553     * @return false if there is no headset connected
554     *               or on error, true otherwise
555     */
556    public boolean stopVoiceRecognition(BluetoothDevice device) {
557        if (DBG) log("stopVoiceRecognition()");
558        if (mService != null && isEnabled() &&
559            isValidDevice(device)) {
560            try {
561                return mService.stopVoiceRecognition(device);
562            } catch (RemoteException e) {
563                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
564            }
565        }
566        if (mService == null) Log.w(TAG, "Proxy not attached to service");
567        return false;
568    }
569
570    /**
571     * Check if Bluetooth SCO audio is connected.
572     *
573     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
574     *
575     * @param device Bluetooth headset
576     * @return true if SCO is connected,
577     *         false otherwise or on error
578     */
579    public boolean isAudioConnected(BluetoothDevice device) {
580        if (VDBG) log("isAudioConnected()");
581        if (mService != null && isEnabled() &&
582            isValidDevice(device)) {
583            try {
584              return mService.isAudioConnected(device);
585            } catch (RemoteException e) {
586              Log.e(TAG,  Log.getStackTraceString(new Throwable()));
587            }
588        }
589        if (mService == null) Log.w(TAG, "Proxy not attached to service");
590        return false;
591    }
592
593    /**
594     * Get battery usage hint for Bluetooth Headset service.
595     * This is a monotonically increasing integer. Wraps to 0 at
596     * Integer.MAX_INT, and at boot.
597     * Current implementation returns the number of AT commands handled since
598     * boot. This is a good indicator for spammy headset/handsfree units that
599     * can keep the device awake by polling for cellular status updates. As a
600     * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
601     *
602     * @param device the bluetooth headset.
603     * @return monotonically increasing battery usage hint, or a negative error
604     *         code on error
605     * @hide
606     */
607    public int getBatteryUsageHint(BluetoothDevice device) {
608        if (VDBG) log("getBatteryUsageHint()");
609        if (mService != null && isEnabled() &&
610            isValidDevice(device)) {
611            try {
612                return mService.getBatteryUsageHint(device);
613            } catch (RemoteException e) {
614                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
615            }
616        }
617        if (mService == null) Log.w(TAG, "Proxy not attached to service");
618        return -1;
619    }
620
621    /**
622     * Indicates if current platform supports voice dialing over bluetooth SCO.
623     *
624     * @return true if voice dialing over bluetooth is supported, false otherwise.
625     * @hide
626     */
627    public static boolean isBluetoothVoiceDialingEnabled(Context context) {
628        return context.getResources().getBoolean(
629                com.android.internal.R.bool.config_bluetooth_sco_off_call);
630    }
631
632    /**
633     * Accept the incoming connection.
634     * Note: This is an internal function and shouldn't be exposed
635     *
636     * @hide
637     */
638    public boolean acceptIncomingConnect(BluetoothDevice device) {
639        if (DBG) log("acceptIncomingConnect");
640        if (mService != null && isEnabled()) {
641            try {
642                return mService.acceptIncomingConnect(device);
643            } catch (RemoteException e) {Log.e(TAG, e.toString());}
644        } else {
645            Log.w(TAG, "Proxy not attached to service");
646            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
647        }
648        return false;
649    }
650
651    /**
652     * Reject the incoming connection.
653     * @hide
654     */
655    public boolean rejectIncomingConnect(BluetoothDevice device) {
656        if (DBG) log("rejectIncomingConnect");
657        if (mService != null) {
658            try {
659                return mService.rejectIncomingConnect(device);
660            } catch (RemoteException e) {Log.e(TAG, e.toString());}
661        } else {
662            Log.w(TAG, "Proxy not attached to service");
663            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
664        }
665        return false;
666    }
667
668    /**
669     * Get the current audio state of the Headset.
670     * Note: This is an internal function and shouldn't be exposed
671     *
672     * @hide
673     */
674    public int getAudioState(BluetoothDevice device) {
675        if (VDBG) log("getAudioState");
676        if (mService != null && !isDisabled()) {
677            try {
678                return mService.getAudioState(device);
679            } catch (RemoteException e) {Log.e(TAG, e.toString());}
680        } else {
681            Log.w(TAG, "Proxy not attached to service");
682            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
683        }
684        return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
685    }
686
687    /**
688     * Check if Bluetooth SCO audio is connected.
689     *
690     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
691     *
692     * @return true if SCO is connected,
693     *         false otherwise or on error
694     * @hide
695     */
696    public boolean isAudioOn() {
697        if (VDBG) log("isAudioOn()");
698        if (mService != null && isEnabled()) {
699            try {
700              return mService.isAudioOn();
701            } catch (RemoteException e) {
702              Log.e(TAG,  Log.getStackTraceString(new Throwable()));
703            }
704        }
705        if (mService == null) Log.w(TAG, "Proxy not attached to service");
706        return false;
707
708    }
709
710    /**
711     * Initiates a connection of headset audio.
712     * It setup SCO channel with remote connected headset device.
713     *
714     * @return true if successful
715     *         false if there was some error such as
716     *               there is no connected headset
717     * @hide
718     */
719    public boolean connectAudio() {
720        if (mService != null && isEnabled()) {
721            try {
722                return mService.connectAudio();
723            } catch (RemoteException e) {
724                Log.e(TAG, e.toString());
725            }
726        } else {
727            Log.w(TAG, "Proxy not attached to service");
728            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
729        }
730        return false;
731    }
732
733    /**
734     * Initiates a disconnection of headset audio.
735     * It tears down the SCO channel from remote headset device.
736     *
737     * @return true if successful
738     *         false if there was some error such as
739     *               there is no connected SCO channel
740     * @hide
741     */
742    public boolean disconnectAudio() {
743        if (mService != null && isEnabled()) {
744            try {
745                return mService.disconnectAudio();
746            } catch (RemoteException e) {
747                Log.e(TAG, e.toString());
748            }
749        } else {
750            Log.w(TAG, "Proxy not attached to service");
751            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
752        }
753        return false;
754    }
755
756    /**
757     * Initiates a SCO channel connection with the headset (if connected).
758     * Also initiates a virtual voice call for Handsfree devices as many devices
759     * do not accept SCO audio without a call.
760     * This API allows the handsfree device to be used for routing non-cellular
761     * call audio.
762     *
763     * @param device Remote Bluetooth Device
764     * @return true if successful, false if there was some error.
765     * @hide
766     */
767    public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
768        if (DBG) log("startScoUsingVirtualVoiceCall()");
769        if (mService != null && isEnabled() && isValidDevice(device)) {
770            try {
771                return mService.startScoUsingVirtualVoiceCall(device);
772            } catch (RemoteException e) {
773                Log.e(TAG, e.toString());
774            }
775        } else {
776            Log.w(TAG, "Proxy not attached to service");
777            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
778        }
779        return false;
780    }
781
782    /**
783     * Terminates an ongoing SCO connection and the associated virtual
784     * call.
785     *
786     * @param device Remote Bluetooth Device
787     * @return true if successful, false if there was some error.
788     * @hide
789     */
790    public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
791        if (DBG) log("stopScoUsingVirtualVoiceCall()");
792        if (mService != null && isEnabled() && isValidDevice(device)) {
793            try {
794                return mService.stopScoUsingVirtualVoiceCall(device);
795            } catch (RemoteException e) {
796                Log.e(TAG, e.toString());
797            }
798        } else {
799            Log.w(TAG, "Proxy not attached to service");
800            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
801        }
802        return false;
803    }
804
805    /**
806     * Notify Headset of phone state change.
807     * This is a backdoor for phone app to call BluetoothHeadset since
808     * there is currently not a good way to get precise call state change outside
809     * of phone app.
810     *
811     * @hide
812     */
813    public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
814                                  int type) {
815        if (mService != null && isEnabled()) {
816            try {
817                mService.phoneStateChanged(numActive, numHeld, callState, number, type);
818            } catch (RemoteException e) {
819                Log.e(TAG, e.toString());
820            }
821        } else {
822            Log.w(TAG, "Proxy not attached to service");
823            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
824        }
825    }
826
827    /**
828     * Send Headset of CLCC response
829     *
830     * @hide
831     */
832    public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
833                             String number, int type) {
834        if (mService != null && isEnabled()) {
835            try {
836                mService.clccResponse(index, direction, status, mode, mpty, number, type);
837            } catch (RemoteException e) {
838                Log.e(TAG, e.toString());
839            }
840        } else {
841            Log.w(TAG, "Proxy not attached to service");
842            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
843        }
844    }
845
846    /**
847     * Sends a vendor-specific unsolicited result code to the headset.
848     *
849     * <p>The actual string to be sent is <code>command + ": " + arg</code>.
850     * For example, if {@code command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg}
851     * is {@code "0"}, the string <code>"+ANDROID: 0"</code> will be sent.
852     *
853     * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}.
854     *
855     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
856     *
857     * @param device Bluetooth headset.
858     * @param command A vendor-specific command.
859     * @param arg The argument that will be attached to the command.
860     * @return {@code false} if there is no headset connected, or if the command is not an allowed
861     *         vendor-specific unsolicited result code, or on error. {@code true} otherwise.
862     * @throws IllegalArgumentException if {@code command} is {@code null}.
863     */
864    public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
865            String arg) {
866        if (DBG) {
867            log("sendVendorSpecificResultCode()");
868        }
869        if (command == null) {
870            throw new IllegalArgumentException("command is null");
871        }
872        if (mService != null && isEnabled() &&
873                isValidDevice(device)) {
874            try {
875                return mService.sendVendorSpecificResultCode(device, command, arg);
876            } catch (RemoteException e) {
877                Log.e(TAG, Log.getStackTraceString(new Throwable()));
878            }
879        }
880        if (mService == null) {
881            Log.w(TAG, "Proxy not attached to service");
882        }
883        return false;
884    }
885
886    /**
887     * enable WBS codec setting.
888     *
889     * @return true if successful
890     *         false if there was some error such as
891     *               there is no connected headset
892     * @hide
893     */
894    public boolean enableWBS() {
895        if (mService != null && isEnabled()) {
896            try {
897                return mService.enableWBS();
898            } catch (RemoteException e) {
899                Log.e(TAG, e.toString());
900            }
901        } else {
902            Log.w(TAG, "Proxy not attached to service");
903            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
904        }
905        return false;
906    }
907
908    /**
909     * disable WBS codec settting. It set NBS codec.
910     *
911     * @return true if successful
912     *         false if there was some error such as
913     *               there is no connected headset
914     * @hide
915     */
916    public boolean disableWBS() {
917        if (mService != null && isEnabled()) {
918            try {
919                return mService.disableWBS();
920            } catch (RemoteException e) {
921                Log.e(TAG, e.toString());
922            }
923        } else {
924            Log.w(TAG, "Proxy not attached to service");
925            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
926        }
927        return false;
928    }
929
930    private final IBluetoothProfileServiceConnection mConnection
931            = new IBluetoothProfileServiceConnection.Stub()  {
932        @Override
933        public void onServiceConnected(ComponentName className, IBinder service) {
934            if (DBG) Log.d(TAG, "Proxy object connected");
935            mService = IBluetoothHeadset.Stub.asInterface(service);
936            mHandler.sendMessage(mHandler.obtainMessage(
937                    MESSAGE_HEADSET_SERVICE_CONNECTED));
938        }
939        @Override
940        public void onServiceDisconnected(ComponentName className) {
941            if (DBG) Log.d(TAG, "Proxy object disconnected");
942            mService = null;
943            mHandler.sendMessage(mHandler.obtainMessage(
944                    MESSAGE_HEADSET_SERVICE_DISCONNECTED));
945        }
946    };
947
948    private boolean isEnabled() {
949       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
950       return false;
951    }
952
953    private boolean isDisabled() {
954       if (mAdapter.getState() == BluetoothAdapter.STATE_OFF) return true;
955       return false;
956    }
957
958    private boolean isValidDevice(BluetoothDevice device) {
959       if (device == null) return false;
960
961       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
962       return false;
963    }
964
965    private static void log(String msg) {
966        Log.d(TAG, msg);
967    }
968
969    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
970        @Override
971        public void handleMessage(Message msg) {
972            switch (msg.what) {
973                case MESSAGE_HEADSET_SERVICE_CONNECTED: {
974                    if (mServiceListener != null) {
975                        mServiceListener.onServiceConnected(BluetoothProfile.HEADSET,
976                                BluetoothHeadset.this);
977                    }
978                    break;
979                }
980                case MESSAGE_HEADSET_SERVICE_DISCONNECTED: {
981                    if (mServiceListener != null) {
982                        mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
983                    }
984                    break;
985                }
986            }
987        }
988    };
989}
990