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