BluetoothHeadset.java revision f2e6b13620f3ebbb94166834abaaabcc08a403b7
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 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_CONNECTED = 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     * @hide
200     */
201    public static final int STATE_AUDIO_CONNECTING = 12;
202
203    /**
204     * Headset state when SCO audio is not connected
205     * This state can be one of
206     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
207     * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
208     */
209    public static final int STATE_AUDIO_DISCONNECTED = 11;
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     * {@link #EXTRA_STATE} will be set to {@link #STATE_AUDIO_CONNECTED}
381     * when the audio connection is established,
382     * and to {@link #STATE_AUDIO_DISCONNECTED} in case of failure.
383     *
384     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
385     *
386     * @param device Bluetooth headset
387     * @return false if there is no headset connected of if the
388     *               connected headset doesn't support voice recognition
389     *               or on error, true otherwise
390     */
391    public boolean startVoiceRecognition(BluetoothDevice device) {
392        if (DBG) log("startVoiceRecognition()");
393        if (mService != null && isEnabled() &&
394            isValidDevice(device)) {
395            try {
396                return mService.startVoiceRecognition(device);
397            } catch (RemoteException e) {
398                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
399            }
400        }
401        if (mService == null) Log.w(TAG, "Proxy not attached to service");
402        return false;
403    }
404
405    /**
406     * Stop Bluetooth Voice Recognition mode, and shut down the
407     * Bluetooth audio path.
408     *
409     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
410     *
411     * @param device Bluetooth headset
412     * @return false if there is no headset connected
413     *               or on error, true otherwise
414     */
415    public boolean stopVoiceRecognition(BluetoothDevice device) {
416        if (DBG) log("stopVoiceRecognition()");
417        if (mService != null && isEnabled() &&
418            isValidDevice(device)) {
419            try {
420                return mService.stopVoiceRecognition(device);
421            } catch (RemoteException e) {
422                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
423            }
424        }
425        if (mService == null) Log.w(TAG, "Proxy not attached to service");
426        return false;
427    }
428
429    /**
430     * Check if Bluetooth SCO audio is connected.
431     *
432     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
433     *
434     * @param device Bluetooth headset
435     * @return true if SCO is connected,
436     *         false otherwise or on error
437     */
438    public boolean isAudioConnected(BluetoothDevice device) {
439        if (DBG) log("isAudioConnected()");
440        if (mService != null && isEnabled() &&
441            isValidDevice(device)) {
442            try {
443              return mService.isAudioConnected(device);
444            } catch (RemoteException e) {
445              Log.e(TAG,  Log.getStackTraceString(new Throwable()));
446            }
447        }
448        if (mService == null) Log.w(TAG, "Proxy not attached to service");
449        return false;
450    }
451
452    /**
453     * Get battery usage hint for Bluetooth Headset service.
454     * This is a monotonically increasing integer. Wraps to 0 at
455     * Integer.MAX_INT, and at boot.
456     * Current implementation returns the number of AT commands handled since
457     * boot. This is a good indicator for spammy headset/handsfree units that
458     * can keep the device awake by polling for cellular status updates. As a
459     * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
460     *
461     * @param device the bluetooth headset.
462     * @return monotonically increasing battery usage hint, or a negative error
463     *         code on error
464     * @hide
465     */
466    public int getBatteryUsageHint(BluetoothDevice device) {
467        if (DBG) log("getBatteryUsageHint()");
468        if (mService != null && isEnabled() &&
469            isValidDevice(device)) {
470            try {
471                return mService.getBatteryUsageHint(device);
472            } catch (RemoteException e) {
473                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
474            }
475        }
476        if (mService == null) Log.w(TAG, "Proxy not attached to service");
477        return -1;
478    }
479
480    /**
481     * Indicates if current platform supports voice dialing over bluetooth SCO.
482     *
483     * @return true if voice dialing over bluetooth is supported, false otherwise.
484     * @hide
485     */
486    public static boolean isBluetoothVoiceDialingEnabled(Context context) {
487        return context.getResources().getBoolean(
488                com.android.internal.R.bool.config_bluetooth_sco_off_call);
489    }
490
491    /**
492     * Cancel the outgoing connection.
493     * Note: This is an internal function and shouldn't be exposed
494     *
495     * @hide
496     */
497    public boolean cancelConnectThread() {
498        if (DBG) log("cancelConnectThread");
499        if (mService != null && isEnabled()) {
500            try {
501                return mService.cancelConnectThread();
502            } catch (RemoteException e) {Log.e(TAG, e.toString());}
503        } else {
504            Log.w(TAG, "Proxy not attached to service");
505            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
506        }
507        return false;
508    }
509
510    /**
511     * Accept the incoming connection.
512     * Note: This is an internal function and shouldn't be exposed
513     *
514     * @hide
515     */
516    public boolean acceptIncomingConnect(BluetoothDevice device) {
517        if (DBG) log("acceptIncomingConnect");
518        if (mService != null && isEnabled()) {
519            try {
520                return mService.acceptIncomingConnect(device);
521            } catch (RemoteException e) {Log.e(TAG, e.toString());}
522        } else {
523            Log.w(TAG, "Proxy not attached to service");
524            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
525        }
526        return false;
527    }
528
529    /**
530     * Create the connect thread for the incoming connection.
531     * Note: This is an internal function and shouldn't be exposed
532     *
533     * @hide
534     */
535    public boolean createIncomingConnect(BluetoothDevice device) {
536        if (DBG) log("createIncomingConnect");
537        if (mService != null && isEnabled()) {
538            try {
539                return mService.createIncomingConnect(device);
540            } catch (RemoteException e) {Log.e(TAG, e.toString());}
541        } else {
542            Log.w(TAG, "Proxy not attached to service");
543            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
544        }
545        return false;
546    }
547
548    /**
549     * Connect to a Bluetooth Headset.
550     * Note: This is an internal function and shouldn't be exposed
551     *
552     * @hide
553     */
554    public boolean connectHeadsetInternal(BluetoothDevice device) {
555        if (DBG) log("connectHeadsetInternal");
556        if (mService != null && isEnabled()) {
557            try {
558                return mService.connectHeadsetInternal(device);
559            } catch (RemoteException e) {Log.e(TAG, e.toString());}
560        } else {
561            Log.w(TAG, "Proxy not attached to service");
562            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
563        }
564        return false;
565    }
566
567    /**
568     * Disconnect a Bluetooth Headset.
569     * Note: This is an internal function and shouldn't be exposed
570     *
571     * @hide
572     */
573    public boolean disconnectHeadsetInternal(BluetoothDevice device) {
574        if (DBG) log("disconnectHeadsetInternal");
575        if (mService != null && isEnabled()) {
576            try {
577                 return mService.disconnectHeadsetInternal(device);
578            } catch (RemoteException e) {Log.e(TAG, e.toString());}
579        } else {
580            Log.w(TAG, "Proxy not attached to service");
581            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
582        }
583        return false;
584    }
585
586    /**
587     * Set the audio state of the Headset.
588     * Note: This is an internal function and shouldn't be exposed
589     *
590     * @hide
591     */
592    public boolean setAudioState(BluetoothDevice device, int state) {
593        if (DBG) log("setAudioState");
594        if (mService != null && isEnabled()) {
595            try {
596                return mService.setAudioState(device, state);
597            } catch (RemoteException e) {Log.e(TAG, e.toString());}
598        } else {
599            Log.w(TAG, "Proxy not attached to service");
600            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
601        }
602        return false;
603    }
604
605    /**
606     * Get the current audio state of the Headset.
607     * Note: This is an internal function and shouldn't be exposed
608     *
609     * @hide
610     */
611    public int getAudioState(BluetoothDevice device) {
612        if (DBG) log("getAudioState");
613        if (mService != null && isEnabled()) {
614            try {
615                return mService.getAudioState(device);
616            } catch (RemoteException e) {Log.e(TAG, e.toString());}
617        } else {
618            Log.w(TAG, "Proxy not attached to service");
619            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
620        }
621        return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
622    }
623
624    /**
625     * Initiates a Virtual Voice Call to the handsfree device (if connected).
626     * Allows the handsfree device to be used for routing non-cellular call audio
627     *
628     * @param device Remote Bluetooth Device
629     * @return true if successful, false if there was some error.
630     * @hide
631     */
632    public boolean startVirtualVoiceCall(BluetoothDevice device) {
633        if (DBG) log("startVirtualVoiceCall()");
634        if (mService != null && isEnabled() && isValidDevice(device)) {
635            try {
636                return mService.startVirtualVoiceCall(device);
637            } catch (RemoteException e) {
638                Log.e(TAG, e.toString());
639            }
640        } else {
641            Log.w(TAG, "Proxy not attached to service");
642            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
643        }
644        return false;
645    }
646
647    /**
648     * Terminates an ongoing Virtual Voice Call to the handsfree device (if connected).
649     *
650     * @param device Remote Bluetooth Device
651     * @return true if successful, false if there was some error.
652     * @hide
653     */
654    public boolean stopVirtualVoiceCall(BluetoothDevice device) {
655        if (DBG) log("stopVirtualVoiceCall()");
656        if (mService != null && isEnabled() && isValidDevice(device)) {
657            try {
658                return mService.stopVirtualVoiceCall(device);
659            } catch (RemoteException e) {
660                Log.e(TAG, e.toString());
661            }
662        } else {
663            Log.w(TAG, "Proxy not attached to service");
664            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
665        }
666        return false;
667    }
668
669    private ServiceConnection mConnection = new ServiceConnection() {
670        public void onServiceConnected(ComponentName className, IBinder service) {
671            if (DBG) Log.d(TAG, "Proxy object connected");
672            mService = IBluetoothHeadset.Stub.asInterface(service);
673
674            if (mServiceListener != null) {
675                mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, BluetoothHeadset.this);
676            }
677        }
678        public void onServiceDisconnected(ComponentName className) {
679            if (DBG) Log.d(TAG, "Proxy object disconnected");
680            mService = null;
681            if (mServiceListener != null) {
682                mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
683            }
684        }
685    };
686
687    private boolean isEnabled() {
688       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
689       return false;
690    }
691
692    private boolean isValidDevice(BluetoothDevice device) {
693       if (device == null) return false;
694
695       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
696       return false;
697    }
698
699    private static void log(String msg) {
700        Log.d(TAG, msg);
701    }
702}
703