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        mServiceListener = null;
249    }
250
251    /**
252     * Initiate connection to a profile of the remote bluetooth device.
253     *
254     * <p> Currently, the system supports only 1 connection to the
255     * headset/handsfree profile. The API will automatically disconnect connected
256     * devices before connecting.
257     *
258     * <p> This API returns false in scenarios like the profile on the
259     * device is already connected or Bluetooth is not turned on.
260     * When this API returns true, it is guaranteed that
261     * connection state intent for the profile will be broadcasted with
262     * the state. Users can get the connection state of the profile
263     * from this intent.
264     *
265     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
266     * permission.
267     *
268     * @param device Remote Bluetooth Device
269     * @return false on immediate error,
270     *               true otherwise
271     * @hide
272     */
273    public boolean connect(BluetoothDevice device) {
274        if (DBG) log("connect(" + device + ")");
275        if (mService != null && isEnabled() &&
276            isValidDevice(device)) {
277            try {
278                return mService.connect(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     * Initiate disconnection from a profile
290     *
291     * <p> This API will return false in scenarios like the profile on the
292     * Bluetooth device is not in connected state etc. When this API returns,
293     * true, it is guaranteed that the connection state change
294     * intent will be broadcasted with the state. Users can get the
295     * disconnection state of the profile from this intent.
296     *
297     * <p> If the disconnection is initiated by a remote device, the state
298     * will transition from {@link #STATE_CONNECTED} to
299     * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
300     * host (local) device the state will transition from
301     * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
302     * state {@link #STATE_DISCONNECTED}. The transition to
303     * {@link #STATE_DISCONNECTING} can be used to distinguish between the
304     * two scenarios.
305     *
306     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
307     * permission.
308     *
309     * @param device Remote Bluetooth Device
310     * @return false on immediate error,
311     *               true otherwise
312     * @hide
313     */
314    public boolean disconnect(BluetoothDevice device) {
315        if (DBG) log("disconnect(" + device + ")");
316        if (mService != null && isEnabled() &&
317            isValidDevice(device)) {
318            try {
319                return mService.disconnect(device);
320            } catch (RemoteException e) {
321              Log.e(TAG, Log.getStackTraceString(new Throwable()));
322              return false;
323            }
324        }
325        if (mService == null) Log.w(TAG, "Proxy not attached to service");
326        return false;
327    }
328
329    /**
330     * {@inheritDoc}
331     */
332    public List<BluetoothDevice> getConnectedDevices() {
333        if (DBG) log("getConnectedDevices()");
334        if (mService != null && isEnabled()) {
335            try {
336                return mService.getConnectedDevices();
337            } catch (RemoteException e) {
338                Log.e(TAG, Log.getStackTraceString(new Throwable()));
339                return new ArrayList<BluetoothDevice>();
340            }
341        }
342        if (mService == null) Log.w(TAG, "Proxy not attached to service");
343        return new ArrayList<BluetoothDevice>();
344    }
345
346    /**
347     * {@inheritDoc}
348     */
349    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
350        if (DBG) log("getDevicesMatchingStates()");
351        if (mService != null && isEnabled()) {
352            try {
353                return mService.getDevicesMatchingConnectionStates(states);
354            } catch (RemoteException e) {
355                Log.e(TAG, Log.getStackTraceString(new Throwable()));
356                return new ArrayList<BluetoothDevice>();
357            }
358        }
359        if (mService == null) Log.w(TAG, "Proxy not attached to service");
360        return new ArrayList<BluetoothDevice>();
361    }
362
363    /**
364     * {@inheritDoc}
365     */
366    public int getConnectionState(BluetoothDevice device) {
367        if (DBG) log("getConnectionState(" + device + ")");
368        if (mService != null && isEnabled() &&
369            isValidDevice(device)) {
370            try {
371                return mService.getConnectionState(device);
372            } catch (RemoteException e) {
373                Log.e(TAG, Log.getStackTraceString(new Throwable()));
374                return BluetoothProfile.STATE_DISCONNECTED;
375            }
376        }
377        if (mService == null) Log.w(TAG, "Proxy not attached to service");
378        return BluetoothProfile.STATE_DISCONNECTED;
379    }
380
381    /**
382     * Set priority of the profile
383     *
384     * <p> The device should already be paired.
385     *  Priority can be one of {@link #PRIORITY_ON} or
386     * {@link #PRIORITY_OFF},
387     *
388     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
389     * permission.
390     *
391     * @param device Paired bluetooth device
392     * @param priority
393     * @return true if priority is set, false on error
394     * @hide
395     */
396    public boolean setPriority(BluetoothDevice device, int priority) {
397        if (DBG) log("setPriority(" + device + ", " + priority + ")");
398        if (mService != null && isEnabled() &&
399            isValidDevice(device)) {
400            if (priority != BluetoothProfile.PRIORITY_OFF &&
401                priority != BluetoothProfile.PRIORITY_ON) {
402              return false;
403            }
404            try {
405                return mService.setPriority(device, priority);
406            } catch (RemoteException e) {
407                Log.e(TAG, Log.getStackTraceString(new Throwable()));
408                return false;
409            }
410        }
411        if (mService == null) Log.w(TAG, "Proxy not attached to service");
412        return false;
413    }
414
415    /**
416     * Get the priority of the profile.
417     *
418     * <p> The priority can be any of:
419     * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
420     * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
421     *
422     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
423     *
424     * @param device Bluetooth device
425     * @return priority of the device
426     * @hide
427     */
428    public int getPriority(BluetoothDevice device) {
429        if (DBG) log("getPriority(" + device + ")");
430        if (mService != null && isEnabled() &&
431            isValidDevice(device)) {
432            try {
433                return mService.getPriority(device);
434            } catch (RemoteException e) {
435                Log.e(TAG, Log.getStackTraceString(new Throwable()));
436                return PRIORITY_OFF;
437            }
438        }
439        if (mService == null) Log.w(TAG, "Proxy not attached to service");
440        return PRIORITY_OFF;
441    }
442
443    /**
444     * Start Bluetooth voice recognition. This methods sends the voice
445     * recognition AT command to the headset and establishes the
446     * audio connection.
447     *
448     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
449     * If this function returns true, this intent will be broadcasted with
450     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
451     *
452     * <p> {@link #EXTRA_STATE} will transition from
453     * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
454     * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
455     * in case of failure to establish the audio connection.
456     *
457     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
458     *
459     * @param device Bluetooth headset
460     * @return false if there is no headset connected of if the
461     *               connected headset doesn't support voice recognition
462     *               or on error, true otherwise
463     */
464    public boolean startVoiceRecognition(BluetoothDevice device) {
465        if (DBG) log("startVoiceRecognition()");
466        if (mService != null && isEnabled() &&
467            isValidDevice(device)) {
468            try {
469                return mService.startVoiceRecognition(device);
470            } catch (RemoteException e) {
471                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
472            }
473        }
474        if (mService == null) Log.w(TAG, "Proxy not attached to service");
475        return false;
476    }
477
478    /**
479     * Stop Bluetooth Voice Recognition mode, and shut down the
480     * Bluetooth audio path.
481     *
482     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
483     *
484     * @param device Bluetooth headset
485     * @return false if there is no headset connected
486     *               or on error, true otherwise
487     */
488    public boolean stopVoiceRecognition(BluetoothDevice device) {
489        if (DBG) log("stopVoiceRecognition()");
490        if (mService != null && isEnabled() &&
491            isValidDevice(device)) {
492            try {
493                return mService.stopVoiceRecognition(device);
494            } catch (RemoteException e) {
495                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
496            }
497        }
498        if (mService == null) Log.w(TAG, "Proxy not attached to service");
499        return false;
500    }
501
502    /**
503     * Check if Bluetooth SCO audio is connected.
504     *
505     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
506     *
507     * @param device Bluetooth headset
508     * @return true if SCO is connected,
509     *         false otherwise or on error
510     */
511    public boolean isAudioConnected(BluetoothDevice device) {
512        if (DBG) log("isAudioConnected()");
513        if (mService != null && isEnabled() &&
514            isValidDevice(device)) {
515            try {
516              return mService.isAudioConnected(device);
517            } catch (RemoteException e) {
518              Log.e(TAG,  Log.getStackTraceString(new Throwable()));
519            }
520        }
521        if (mService == null) Log.w(TAG, "Proxy not attached to service");
522        return false;
523    }
524
525    /**
526     * Get battery usage hint for Bluetooth Headset service.
527     * This is a monotonically increasing integer. Wraps to 0 at
528     * Integer.MAX_INT, and at boot.
529     * Current implementation returns the number of AT commands handled since
530     * boot. This is a good indicator for spammy headset/handsfree units that
531     * can keep the device awake by polling for cellular status updates. As a
532     * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
533     *
534     * @param device the bluetooth headset.
535     * @return monotonically increasing battery usage hint, or a negative error
536     *         code on error
537     * @hide
538     */
539    public int getBatteryUsageHint(BluetoothDevice device) {
540        if (DBG) log("getBatteryUsageHint()");
541        if (mService != null && isEnabled() &&
542            isValidDevice(device)) {
543            try {
544                return mService.getBatteryUsageHint(device);
545            } catch (RemoteException e) {
546                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
547            }
548        }
549        if (mService == null) Log.w(TAG, "Proxy not attached to service");
550        return -1;
551    }
552
553    /**
554     * Indicates if current platform supports voice dialing over bluetooth SCO.
555     *
556     * @return true if voice dialing over bluetooth is supported, false otherwise.
557     * @hide
558     */
559    public static boolean isBluetoothVoiceDialingEnabled(Context context) {
560        return context.getResources().getBoolean(
561                com.android.internal.R.bool.config_bluetooth_sco_off_call);
562    }
563
564    /**
565     * Cancel the outgoing connection.
566     * Note: This is an internal function and shouldn't be exposed
567     *
568     * @hide
569     */
570    public boolean cancelConnectThread() {
571        if (DBG) log("cancelConnectThread");
572        if (mService != null && isEnabled()) {
573            try {
574                return mService.cancelConnectThread();
575            } catch (RemoteException e) {Log.e(TAG, e.toString());}
576        } else {
577            Log.w(TAG, "Proxy not attached to service");
578            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
579        }
580        return false;
581    }
582
583    /**
584     * Accept the incoming connection.
585     * Note: This is an internal function and shouldn't be exposed
586     *
587     * @hide
588     */
589    public boolean acceptIncomingConnect(BluetoothDevice device) {
590        if (DBG) log("acceptIncomingConnect");
591        if (mService != null && isEnabled()) {
592            try {
593                return mService.acceptIncomingConnect(device);
594            } catch (RemoteException e) {Log.e(TAG, e.toString());}
595        } else {
596            Log.w(TAG, "Proxy not attached to service");
597            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
598        }
599        return false;
600    }
601
602    /**
603     * Create the connect thread for the incoming connection.
604     * Note: This is an internal function and shouldn't be exposed
605     *
606     * @hide
607     */
608    public boolean createIncomingConnect(BluetoothDevice device) {
609        if (DBG) log("createIncomingConnect");
610        if (mService != null && isEnabled()) {
611            try {
612                return mService.createIncomingConnect(device);
613            } catch (RemoteException e) {Log.e(TAG, e.toString());}
614        } else {
615            Log.w(TAG, "Proxy not attached to service");
616            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
617        }
618        return false;
619    }
620
621    /**
622     * Reject the incoming connection.
623     * @hide
624     */
625    public boolean rejectIncomingConnect(BluetoothDevice device) {
626        if (DBG) log("rejectIncomingConnect");
627        if (mService != null) {
628            try {
629                return mService.rejectIncomingConnect(device);
630            } catch (RemoteException e) {Log.e(TAG, e.toString());}
631        } else {
632            Log.w(TAG, "Proxy not attached to service");
633            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
634        }
635        return false;
636    }
637
638    /**
639     * Connect to a Bluetooth Headset.
640     * Note: This is an internal function and shouldn't be exposed
641     *
642     * @hide
643     */
644    public boolean connectHeadsetInternal(BluetoothDevice device) {
645        if (DBG) log("connectHeadsetInternal");
646        if (mService != null && isEnabled()) {
647            try {
648                return mService.connectHeadsetInternal(device);
649            } catch (RemoteException e) {Log.e(TAG, e.toString());}
650        } else {
651            Log.w(TAG, "Proxy not attached to service");
652            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
653        }
654        return false;
655    }
656
657    /**
658     * Disconnect a Bluetooth Headset.
659     * Note: This is an internal function and shouldn't be exposed
660     *
661     * @hide
662     */
663    public boolean disconnectHeadsetInternal(BluetoothDevice device) {
664        if (DBG) log("disconnectHeadsetInternal");
665        if (mService != null && !isDisabled()) {
666            try {
667                 return mService.disconnectHeadsetInternal(device);
668            } catch (RemoteException e) {Log.e(TAG, e.toString());}
669        } else {
670            Log.w(TAG, "Proxy not attached to service");
671            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
672        }
673        return false;
674    }
675
676    /**
677     * Set the audio state of the Headset.
678     * Note: This is an internal function and shouldn't be exposed
679     *
680     * @hide
681     */
682    public boolean setAudioState(BluetoothDevice device, int state) {
683        if (DBG) log("setAudioState");
684        if (mService != null && !isDisabled()) {
685            try {
686                return mService.setAudioState(device, state);
687            } catch (RemoteException e) {Log.e(TAG, e.toString());}
688        } else {
689            Log.w(TAG, "Proxy not attached to service");
690            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
691        }
692        return false;
693    }
694
695    /**
696     * Get the current audio state of the Headset.
697     * Note: This is an internal function and shouldn't be exposed
698     *
699     * @hide
700     */
701    public int getAudioState(BluetoothDevice device) {
702        if (DBG) log("getAudioState");
703        if (mService != null && !isDisabled()) {
704            try {
705                return mService.getAudioState(device);
706            } catch (RemoteException e) {Log.e(TAG, e.toString());}
707        } else {
708            Log.w(TAG, "Proxy not attached to service");
709            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
710        }
711        return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
712    }
713
714    /**
715     * Initiates a SCO channel connection with the headset (if connected).
716     * Also initiates a virtual voice call for Handsfree devices as many devices
717     * do not accept SCO audio without a call.
718     * This API allows the handsfree device to be used for routing non-cellular
719     * call audio.
720     *
721     * @param device Remote Bluetooth Device
722     * @return true if successful, false if there was some error.
723     * @hide
724     */
725    public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
726        if (DBG) log("startScoUsingVirtualVoiceCall()");
727        if (mService != null && isEnabled() && isValidDevice(device)) {
728            try {
729                return mService.startScoUsingVirtualVoiceCall(device);
730            } catch (RemoteException e) {
731                Log.e(TAG, e.toString());
732            }
733        } else {
734            Log.w(TAG, "Proxy not attached to service");
735            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
736        }
737        return false;
738    }
739
740    /**
741     * Terminates an ongoing SCO connection and the associated virtual
742     * call.
743     *
744     * @param device Remote Bluetooth Device
745     * @return true if successful, false if there was some error.
746     * @hide
747     */
748    public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
749        if (DBG) log("stopScoUsingVirtualVoiceCall()");
750        if (mService != null && isEnabled() && isValidDevice(device)) {
751            try {
752                return mService.stopScoUsingVirtualVoiceCall(device);
753            } catch (RemoteException e) {
754                Log.e(TAG, e.toString());
755            }
756        } else {
757            Log.w(TAG, "Proxy not attached to service");
758            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
759        }
760        return false;
761    }
762
763    private ServiceConnection mConnection = new ServiceConnection() {
764        public void onServiceConnected(ComponentName className, IBinder service) {
765            if (DBG) Log.d(TAG, "Proxy object connected");
766            mService = IBluetoothHeadset.Stub.asInterface(service);
767
768            if (mServiceListener != null) {
769                mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, BluetoothHeadset.this);
770            }
771        }
772        public void onServiceDisconnected(ComponentName className) {
773            if (DBG) Log.d(TAG, "Proxy object disconnected");
774            mService = null;
775            if (mServiceListener != null) {
776                mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
777            }
778        }
779    };
780
781    private boolean isEnabled() {
782       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
783       return false;
784    }
785
786    private boolean isDisabled() {
787       if (mAdapter.getState() == BluetoothAdapter.STATE_OFF) return true;
788       return false;
789    }
790
791    private boolean isValidDevice(BluetoothDevice device) {
792       if (device == null) return false;
793
794       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
795       return false;
796    }
797
798    private static void log(String msg) {
799        Log.d(TAG, msg);
800    }
801}
802