BluetoothHeadset.java revision 0706fed52075f7f2b25101a40287519ac18d3184
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.content.Intent;
24import android.content.ServiceConnection;
25import android.os.IBinder;
26import android.os.RemoteException;
27import android.util.Log;
28
29import java.util.ArrayList;
30import java.util.List;
31
32/**
33 * Public API for controlling the Bluetooth Headset Service. This includes both
34 * Bluetooth Headset and Handsfree (v1.5) profiles.
35 *
36 * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
37 * Service via IPC.
38 *
39 * <p> Use {@link BluetoothAdapter#getProfileProxy} to get
40 * the BluetoothHeadset proxy object. Use
41 * {@link BluetoothAdapter#closeProfileProxy} to close the service connection.
42 *
43 * <p> Android only supports one connected Bluetooth Headset at a time.
44 * Each method is protected with its appropriate permission.
45 */
46public final class BluetoothHeadset implements BluetoothProfile {
47    private static final String TAG = "BluetoothHeadset";
48    private static final boolean DBG = false;
49
50    /**
51     * Intent used to broadcast the change in connection state of the Headset
52     * profile.
53     *
54     * <p>This intent will have 3 extras:
55     * <ul>
56     *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
57     *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
58     *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
59     * </ul>
60     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
61     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
62     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
63     *
64     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
65     * receive.
66     */
67    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
68    public static final String ACTION_CONNECTION_STATE_CHANGED =
69        "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
70
71    /**
72     * Intent used to broadcast the change in the Audio Connection state of the
73     * A2DP profile.
74     *
75     * <p>This intent will have 3 extras:
76     * <ul>
77     *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
78     *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
79     *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
80     * </ul>
81     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
82     * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED},
83     *
84     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
85     * to receive.
86     */
87    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
88    public static final String ACTION_AUDIO_STATE_CHANGED =
89        "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
90
91
92    /**
93     * Intent used to broadcast that the headset has posted a
94     * vendor-specific event.
95     *
96     * <p>This intent will have 4 extras and 1 category.
97     * <ul>
98     *  <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device
99     *       </li>
100     *  <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor
101     *       specific command </li>
102     *  <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT
103     *       command type which can be one of  {@link #AT_CMD_TYPE_READ},
104     *       {@link #AT_CMD_TYPE_TEST}, or {@link #AT_CMD_TYPE_SET},
105     *       {@link #AT_CMD_TYPE_BASIC},{@link #AT_CMD_TYPE_ACTION}. </li>
106     *  <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command
107     *       arguments. </li>
108     * </ul>
109     *
110     *<p> The category is the Company ID of the vendor defining the
111     * vendor-specific command. {@link BluetoothAssignedNumbers}
112     *
113     * For example, for Plantronics specific events
114     * Category will be {@link #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55
115     *
116     * <p> For example, an AT+XEVENT=foo,3 will get translated into
117     * <ul>
118     *   <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT </li>
119     *   <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li>
120     *   <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li>
121     * </ul>
122     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
123     * to receive.
124     */
125    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
126    public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT =
127            "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
128
129    /**
130     * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
131     * intents that contains the name of the vendor-specific command.
132     */
133    public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD =
134            "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD";
135
136    /**
137     * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
138     * intents that contains the AT command type of the vendor-specific command.
139     */
140    public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE =
141            "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE";
142
143    /**
144     * AT command type READ used with
145     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
146     * For example, AT+VGM?. There are no arguments for this command type.
147     */
148    public static final int AT_CMD_TYPE_READ = 0;
149
150    /**
151     * AT command type TEST used with
152     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
153     * For example, AT+VGM=?. There are no arguments for this command type.
154     */
155    public static final int AT_CMD_TYPE_TEST = 1;
156
157    /**
158     * AT command type SET used with
159     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
160     * For example, AT+VGM=<args>.
161     */
162    public static final int AT_CMD_TYPE_SET = 2;
163
164    /**
165     * AT command type BASIC used with
166     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
167     * For example, ATD. Single character commands and everything following the
168     * character are arguments.
169     */
170    public static final int AT_CMD_TYPE_BASIC = 3;
171
172    /**
173     * AT command type ACTION used with
174     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
175     * For example, AT+CHUP. There are no arguments for action commands.
176     */
177    public static final int AT_CMD_TYPE_ACTION = 4;
178
179    /**
180     * A Parcelable String array extra field in
181     * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains
182     * the arguments to the vendor-specific command.
183     */
184    public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS =
185            "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS";
186
187    /**
188     * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
189     * for the companyId
190     */
191    public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY  =
192            "android.bluetooth.headset.intent.category.companyid";
193
194    /**
195     * Headset state when SCO audio is not connected.
196     * This state can be one of
197     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
198     * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
199     */
200    public static final int STATE_AUDIO_DISCONNECTED = 10;
201
202    /**
203     * Headset state when SCO audio is connecting.
204     * This state can be one of
205     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
206     * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
207     */
208    public static final int STATE_AUDIO_CONNECTING = 11;
209
210    /**
211     * Headset state when SCO audio is connected.
212     * This state can be one of
213     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
214     * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
215     */
216    public static final int STATE_AUDIO_CONNECTED = 12;
217
218
219    private Context mContext;
220    private ServiceListener mServiceListener;
221    private IBluetoothHeadset mService;
222    BluetoothAdapter mAdapter;
223
224    /**
225     * Create a BluetoothHeadset proxy object.
226     */
227    /*package*/ BluetoothHeadset(Context context, ServiceListener l) {
228        mContext = context;
229        mServiceListener = l;
230        mAdapter = BluetoothAdapter.getDefaultAdapter();
231        if (!context.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) {
232            Log.e(TAG, "Could not bind to Bluetooth Headset Service");
233        }
234    }
235
236    /**
237     * Close the connection to the backing service.
238     * Other public functions of BluetoothHeadset will return default error
239     * results once close() has been called. Multiple invocations of close()
240     * are ok.
241     */
242    /*package*/ synchronized void close() {
243        if (DBG) log("close()");
244        if (mConnection != null) {
245            mContext.unbindService(mConnection);
246            mConnection = null;
247        }
248    }
249
250    /**
251     * {@inheritDoc}
252     * @hide
253     */
254    public boolean connect(BluetoothDevice device) {
255        if (DBG) log("connect(" + device + ")");
256        if (mService != null && isEnabled() &&
257            isValidDevice(device)) {
258            try {
259                return mService.connect(device);
260            } catch (RemoteException e) {
261                Log.e(TAG, Log.getStackTraceString(new Throwable()));
262                return false;
263            }
264        }
265        if (mService == null) Log.w(TAG, "Proxy not attached to service");
266        return false;
267    }
268
269    /**
270     * {@inheritDoc}
271     * @hide
272     */
273    public boolean disconnect(BluetoothDevice device) {
274        if (DBG) log("disconnect(" + device + ")");
275        if (mService != null && isEnabled() &&
276            isValidDevice(device)) {
277            try {
278                return mService.disconnect(device);
279            } catch (RemoteException e) {
280              Log.e(TAG, Log.getStackTraceString(new Throwable()));
281              return false;
282            }
283        }
284        if (mService == null) Log.w(TAG, "Proxy not attached to service");
285        return false;
286    }
287
288    /**
289     * {@inheritDoc}
290     */
291    public List<BluetoothDevice> getConnectedDevices() {
292        if (DBG) log("getConnectedDevices()");
293        if (mService != null && isEnabled()) {
294            try {
295                return mService.getConnectedDevices();
296            } catch (RemoteException e) {
297                Log.e(TAG, Log.getStackTraceString(new Throwable()));
298                return new ArrayList<BluetoothDevice>();
299            }
300        }
301        if (mService == null) Log.w(TAG, "Proxy not attached to service");
302        return new ArrayList<BluetoothDevice>();
303    }
304
305    /**
306     * {@inheritDoc}
307     */
308    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
309        if (DBG) log("getDevicesMatchingStates()");
310        if (mService != null && isEnabled()) {
311            try {
312                return mService.getDevicesMatchingConnectionStates(states);
313            } catch (RemoteException e) {
314                Log.e(TAG, Log.getStackTraceString(new Throwable()));
315                return new ArrayList<BluetoothDevice>();
316            }
317        }
318        if (mService == null) Log.w(TAG, "Proxy not attached to service");
319        return new ArrayList<BluetoothDevice>();
320    }
321
322    /**
323     * {@inheritDoc}
324     */
325    public int getConnectionState(BluetoothDevice device) {
326        if (DBG) log("getConnectionState(" + device + ")");
327        if (mService != null && isEnabled() &&
328            isValidDevice(device)) {
329            try {
330                return mService.getConnectionState(device);
331            } catch (RemoteException e) {
332                Log.e(TAG, Log.getStackTraceString(new Throwable()));
333                return BluetoothProfile.STATE_DISCONNECTED;
334            }
335        }
336        if (mService == null) Log.w(TAG, "Proxy not attached to service");
337        return BluetoothProfile.STATE_DISCONNECTED;
338    }
339
340    /**
341     * {@inheritDoc}
342     * @hide
343     */
344    public boolean setPriority(BluetoothDevice device, int priority) {
345        if (DBG) log("setPriority(" + device + ", " + priority + ")");
346        if (mService != null && isEnabled() &&
347            isValidDevice(device)) {
348            if (priority != BluetoothProfile.PRIORITY_OFF &&
349                priority != BluetoothProfile.PRIORITY_ON) {
350              return false;
351            }
352            try {
353                return mService.setPriority(device, priority);
354            } catch (RemoteException e) {
355                Log.e(TAG, Log.getStackTraceString(new Throwable()));
356                return false;
357            }
358        }
359        if (mService == null) Log.w(TAG, "Proxy not attached to service");
360        return false;
361    }
362
363    /**
364     * {@inheritDoc}
365     * @hide
366     */
367    public int getPriority(BluetoothDevice device) {
368        if (DBG) log("getPriority(" + device + ")");
369        if (mService != null && isEnabled() &&
370            isValidDevice(device)) {
371            try {
372                return mService.getPriority(device);
373            } catch (RemoteException e) {
374                Log.e(TAG, Log.getStackTraceString(new Throwable()));
375                return PRIORITY_OFF;
376            }
377        }
378        if (mService == null) Log.w(TAG, "Proxy not attached to service");
379        return PRIORITY_OFF;
380    }
381
382    /**
383     * Start Bluetooth voice recognition. This methods sends the voice
384     * recognition AT command to the headset and establishes the
385     * audio connection.
386     *
387     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
388     * If this function returns true, this intent will be broadcasted with
389     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
390     *
391     * <p> {@link #EXTRA_STATE} will transition from
392     * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
393     * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
394     * in case of failure to establish the audio connection.
395     *
396     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
397     *
398     * @param device Bluetooth headset
399     * @return false if there is no headset connected of if the
400     *               connected headset doesn't support voice recognition
401     *               or on error, true otherwise
402     */
403    public boolean startVoiceRecognition(BluetoothDevice device) {
404        if (DBG) log("startVoiceRecognition()");
405        if (mService != null && isEnabled() &&
406            isValidDevice(device)) {
407            try {
408                return mService.startVoiceRecognition(device);
409            } catch (RemoteException e) {
410                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
411            }
412        }
413        if (mService == null) Log.w(TAG, "Proxy not attached to service");
414        return false;
415    }
416
417    /**
418     * Stop Bluetooth Voice Recognition mode, and shut down the
419     * Bluetooth audio path.
420     *
421     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
422     *
423     * @param device Bluetooth headset
424     * @return false if there is no headset connected
425     *               or on error, true otherwise
426     */
427    public boolean stopVoiceRecognition(BluetoothDevice device) {
428        if (DBG) log("stopVoiceRecognition()");
429        if (mService != null && isEnabled() &&
430            isValidDevice(device)) {
431            try {
432                return mService.stopVoiceRecognition(device);
433            } catch (RemoteException e) {
434                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
435            }
436        }
437        if (mService == null) Log.w(TAG, "Proxy not attached to service");
438        return false;
439    }
440
441    /**
442     * Check if Bluetooth SCO audio is connected.
443     *
444     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
445     *
446     * @param device Bluetooth headset
447     * @return true if SCO is connected,
448     *         false otherwise or on error
449     */
450    public boolean isAudioConnected(BluetoothDevice device) {
451        if (DBG) log("isAudioConnected()");
452        if (mService != null && isEnabled() &&
453            isValidDevice(device)) {
454            try {
455              return mService.isAudioConnected(device);
456            } catch (RemoteException e) {
457              Log.e(TAG,  Log.getStackTraceString(new Throwable()));
458            }
459        }
460        if (mService == null) Log.w(TAG, "Proxy not attached to service");
461        return false;
462    }
463
464    /**
465     * Get battery usage hint for Bluetooth Headset service.
466     * This is a monotonically increasing integer. Wraps to 0 at
467     * Integer.MAX_INT, and at boot.
468     * Current implementation returns the number of AT commands handled since
469     * boot. This is a good indicator for spammy headset/handsfree units that
470     * can keep the device awake by polling for cellular status updates. As a
471     * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
472     *
473     * @param device the bluetooth headset.
474     * @return monotonically increasing battery usage hint, or a negative error
475     *         code on error
476     * @hide
477     */
478    public int getBatteryUsageHint(BluetoothDevice device) {
479        if (DBG) log("getBatteryUsageHint()");
480        if (mService != null && isEnabled() &&
481            isValidDevice(device)) {
482            try {
483                return mService.getBatteryUsageHint(device);
484            } catch (RemoteException e) {
485                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
486            }
487        }
488        if (mService == null) Log.w(TAG, "Proxy not attached to service");
489        return -1;
490    }
491
492    /**
493     * Indicates if current platform supports voice dialing over bluetooth SCO.
494     *
495     * @return true if voice dialing over bluetooth is supported, false otherwise.
496     * @hide
497     */
498    public static boolean isBluetoothVoiceDialingEnabled(Context context) {
499        return context.getResources().getBoolean(
500                com.android.internal.R.bool.config_bluetooth_sco_off_call);
501    }
502
503    /**
504     * Cancel the outgoing connection.
505     * Note: This is an internal function and shouldn't be exposed
506     *
507     * @hide
508     */
509    public boolean cancelConnectThread() {
510        if (DBG) log("cancelConnectThread");
511        if (mService != null && isEnabled()) {
512            try {
513                return mService.cancelConnectThread();
514            } catch (RemoteException e) {Log.e(TAG, e.toString());}
515        } else {
516            Log.w(TAG, "Proxy not attached to service");
517            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
518        }
519        return false;
520    }
521
522    /**
523     * Accept the incoming connection.
524     * Note: This is an internal function and shouldn't be exposed
525     *
526     * @hide
527     */
528    public boolean acceptIncomingConnect(BluetoothDevice device) {
529        if (DBG) log("acceptIncomingConnect");
530        if (mService != null && isEnabled()) {
531            try {
532                return mService.acceptIncomingConnect(device);
533            } catch (RemoteException e) {Log.e(TAG, e.toString());}
534        } else {
535            Log.w(TAG, "Proxy not attached to service");
536            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
537        }
538        return false;
539    }
540
541    /**
542     * Create the connect thread for the incoming connection.
543     * Note: This is an internal function and shouldn't be exposed
544     *
545     * @hide
546     */
547    public boolean createIncomingConnect(BluetoothDevice device) {
548        if (DBG) log("createIncomingConnect");
549        if (mService != null && isEnabled()) {
550            try {
551                return mService.createIncomingConnect(device);
552            } catch (RemoteException e) {Log.e(TAG, e.toString());}
553        } else {
554            Log.w(TAG, "Proxy not attached to service");
555            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
556        }
557        return false;
558    }
559
560    /**
561     * Connect to a Bluetooth Headset.
562     * Note: This is an internal function and shouldn't be exposed
563     *
564     * @hide
565     */
566    public boolean connectHeadsetInternal(BluetoothDevice device) {
567        if (DBG) log("connectHeadsetInternal");
568        if (mService != null && isEnabled()) {
569            try {
570                return mService.connectHeadsetInternal(device);
571            } catch (RemoteException e) {Log.e(TAG, e.toString());}
572        } else {
573            Log.w(TAG, "Proxy not attached to service");
574            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
575        }
576        return false;
577    }
578
579    /**
580     * Disconnect a Bluetooth Headset.
581     * Note: This is an internal function and shouldn't be exposed
582     *
583     * @hide
584     */
585    public boolean disconnectHeadsetInternal(BluetoothDevice device) {
586        if (DBG) log("disconnectHeadsetInternal");
587        if (mService != null && isEnabled()) {
588            try {
589                 return mService.disconnectHeadsetInternal(device);
590            } catch (RemoteException e) {Log.e(TAG, e.toString());}
591        } else {
592            Log.w(TAG, "Proxy not attached to service");
593            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
594        }
595        return false;
596    }
597
598    /**
599     * Set the audio state of the Headset.
600     * Note: This is an internal function and shouldn't be exposed
601     *
602     * @hide
603     */
604    public boolean setAudioState(BluetoothDevice device, int state) {
605        if (DBG) log("setAudioState");
606        if (mService != null && isEnabled()) {
607            try {
608                return mService.setAudioState(device, state);
609            } catch (RemoteException e) {Log.e(TAG, e.toString());}
610        } else {
611            Log.w(TAG, "Proxy not attached to service");
612            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
613        }
614        return false;
615    }
616
617    /**
618     * Get the current audio state of the Headset.
619     * Note: This is an internal function and shouldn't be exposed
620     *
621     * @hide
622     */
623    public int getAudioState(BluetoothDevice device) {
624        if (DBG) log("getAudioState");
625        if (mService != null && isEnabled()) {
626            try {
627                return mService.getAudioState(device);
628            } catch (RemoteException e) {Log.e(TAG, e.toString());}
629        } else {
630            Log.w(TAG, "Proxy not attached to service");
631            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
632        }
633        return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
634    }
635
636    /**
637     * Initiates a SCO channel connection with the headset (if connected).
638     * Also initiates a virtual voice call for Handsfree devices as many devices
639     * do not accept SCO audio without a call.
640     * This API allows the handsfree device to be used for routing non-cellular
641     * call audio.
642     *
643     * @param device Remote Bluetooth Device
644     * @return true if successful, false if there was some error.
645     * @hide
646     */
647    public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
648        if (DBG) log("startScoUsingVirtualVoiceCall()");
649        if (mService != null && isEnabled() && isValidDevice(device)) {
650            try {
651                return mService.startScoUsingVirtualVoiceCall(device);
652            } catch (RemoteException e) {
653                Log.e(TAG, e.toString());
654            }
655        } else {
656            Log.w(TAG, "Proxy not attached to service");
657            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
658        }
659        return false;
660    }
661
662    /**
663     * Terminates an ongoing SCO connection and the associated virtual
664     * call.
665     *
666     * @param device Remote Bluetooth Device
667     * @return true if successful, false if there was some error.
668     * @hide
669     */
670    public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
671        if (DBG) log("stopScoUsingVirtualVoiceCall()");
672        if (mService != null && isEnabled() && isValidDevice(device)) {
673            try {
674                return mService.stopScoUsingVirtualVoiceCall(device);
675            } catch (RemoteException e) {
676                Log.e(TAG, e.toString());
677            }
678        } else {
679            Log.w(TAG, "Proxy not attached to service");
680            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
681        }
682        return false;
683    }
684
685    private ServiceConnection mConnection = new ServiceConnection() {
686        public void onServiceConnected(ComponentName className, IBinder service) {
687            if (DBG) Log.d(TAG, "Proxy object connected");
688            mService = IBluetoothHeadset.Stub.asInterface(service);
689
690            if (mServiceListener != null) {
691                mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, BluetoothHeadset.this);
692            }
693        }
694        public void onServiceDisconnected(ComponentName className) {
695            if (DBG) Log.d(TAG, "Proxy object disconnected");
696            mService = null;
697            if (mServiceListener != null) {
698                mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
699            }
700        }
701    };
702
703    private boolean isEnabled() {
704       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
705       return false;
706    }
707
708    private boolean isValidDevice(BluetoothDevice device) {
709       if (device == null) return false;
710
711       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
712       return false;
713    }
714
715    private static void log(String msg) {
716        Log.d(TAG, msg);
717    }
718}
719