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 = true;
49    private static final boolean VDBG = false;
50
51    /**
52     * Intent used to broadcast the change in connection state of the Headset
53     * profile.
54     *
55     * <p>This intent will have 3 extras:
56     * <ul>
57     *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
58     *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
59     *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
60     * </ul>
61     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
62     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
63     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
64     *
65     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
66     * receive.
67     */
68    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
69    public static final String ACTION_CONNECTION_STATE_CHANGED =
70        "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
71
72    /**
73     * Intent used to broadcast the change in the Audio Connection state of the
74     * A2DP profile.
75     *
76     * <p>This intent will have 3 extras:
77     * <ul>
78     *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
79     *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
80     *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
81     * </ul>
82     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
83     * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED},
84     *
85     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
86     * to receive.
87     */
88    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
89    public static final String ACTION_AUDIO_STATE_CHANGED =
90        "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
91
92
93    /**
94     * Intent used to broadcast that the headset has posted a
95     * vendor-specific event.
96     *
97     * <p>This intent will have 4 extras and 1 category.
98     * <ul>
99     *  <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device
100     *       </li>
101     *  <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor
102     *       specific command </li>
103     *  <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT
104     *       command type which can be one of  {@link #AT_CMD_TYPE_READ},
105     *       {@link #AT_CMD_TYPE_TEST}, or {@link #AT_CMD_TYPE_SET},
106     *       {@link #AT_CMD_TYPE_BASIC},{@link #AT_CMD_TYPE_ACTION}. </li>
107     *  <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command
108     *       arguments. </li>
109     * </ul>
110     *
111     *<p> The category is the Company ID of the vendor defining the
112     * vendor-specific command. {@link BluetoothAssignedNumbers}
113     *
114     * For example, for Plantronics specific events
115     * Category will be {@link #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55
116     *
117     * <p> For example, an AT+XEVENT=foo,3 will get translated into
118     * <ul>
119     *   <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT </li>
120     *   <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li>
121     *   <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li>
122     * </ul>
123     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
124     * to receive.
125     */
126    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
127    public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT =
128            "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
129
130    /**
131     * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
132     * intents that contains the name of the vendor-specific command.
133     */
134    public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD =
135            "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD";
136
137    /**
138     * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
139     * intents that contains the AT command type of the vendor-specific command.
140     */
141    public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE =
142            "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE";
143
144    /**
145     * AT command type READ used with
146     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
147     * For example, AT+VGM?. There are no arguments for this command type.
148     */
149    public static final int AT_CMD_TYPE_READ = 0;
150
151    /**
152     * AT command type TEST used with
153     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
154     * For example, AT+VGM=?. There are no arguments for this command type.
155     */
156    public static final int AT_CMD_TYPE_TEST = 1;
157
158    /**
159     * AT command type SET used with
160     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
161     * For example, AT+VGM=<args>.
162     */
163    public static final int AT_CMD_TYPE_SET = 2;
164
165    /**
166     * AT command type BASIC used with
167     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
168     * For example, ATD. Single character commands and everything following the
169     * character are arguments.
170     */
171    public static final int AT_CMD_TYPE_BASIC = 3;
172
173    /**
174     * AT command type ACTION used with
175     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
176     * For example, AT+CHUP. There are no arguments for action commands.
177     */
178    public static final int AT_CMD_TYPE_ACTION = 4;
179
180    /**
181     * A Parcelable String array extra field in
182     * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains
183     * the arguments to the vendor-specific command.
184     */
185    public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS =
186            "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS";
187
188    /**
189     * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
190     * for the companyId
191     */
192    public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY  =
193            "android.bluetooth.headset.intent.category.companyid";
194
195    /**
196     * A vendor-specific command for unsolicited result code.
197     */
198    public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID";
199
200    /**
201     * Headset state when SCO audio is not connected.
202     * This state can be one of
203     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
204     * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
205     */
206    public static final int STATE_AUDIO_DISCONNECTED = 10;
207
208    /**
209     * Headset state when SCO audio is connecting.
210     * This state can be one of
211     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
212     * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
213     */
214    public static final int STATE_AUDIO_CONNECTING = 11;
215
216    /**
217     * Headset state when SCO audio is connected.
218     * This state can be one of
219     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
220     * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
221     */
222    public static final int STATE_AUDIO_CONNECTED = 12;
223
224
225    private Context mContext;
226    private ServiceListener mServiceListener;
227    private IBluetoothHeadset mService;
228    private BluetoothAdapter mAdapter;
229
230    final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
231            new IBluetoothStateChangeCallback.Stub() {
232                public void onBluetoothStateChange(boolean up) {
233                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
234                    if (!up) {
235                        if (VDBG) Log.d(TAG,"Unbinding service...");
236                        synchronized (mConnection) {
237                            try {
238                                mService = null;
239                                mContext.unbindService(mConnection);
240                            } catch (Exception re) {
241                                Log.e(TAG,"",re);
242                            }
243                        }
244                    } else {
245                        synchronized (mConnection) {
246                            try {
247                                if (mService == null) {
248                                    if (VDBG) Log.d(TAG,"Binding service...");
249                                    doBind();
250                                }
251                            } catch (Exception re) {
252                                Log.e(TAG,"",re);
253                            }
254                        }
255                    }
256                }
257        };
258
259    /**
260     * Create a BluetoothHeadset proxy object.
261     */
262    /*package*/ BluetoothHeadset(Context context, ServiceListener l) {
263        mContext = context;
264        mServiceListener = l;
265        mAdapter = BluetoothAdapter.getDefaultAdapter();
266
267        IBluetoothManager mgr = mAdapter.getBluetoothManager();
268        if (mgr != null) {
269            try {
270                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
271            } catch (RemoteException e) {
272                Log.e(TAG,"",e);
273            }
274        }
275
276        doBind();
277    }
278
279    boolean doBind() {
280        Intent intent = new Intent(IBluetoothHeadset.class.getName());
281        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
282        intent.setComponent(comp);
283        if (comp == null || !mContext.bindService(intent, mConnection, 0)) {
284            Log.e(TAG, "Could not bind to Bluetooth Headset Service with " + intent);
285            return false;
286        }
287        return true;
288    }
289
290    /**
291     * Close the connection to the backing service.
292     * Other public functions of BluetoothHeadset will return default error
293     * results once close() has been called. Multiple invocations of close()
294     * are ok.
295     */
296    /*package*/ void close() {
297        if (VDBG) log("close()");
298
299        IBluetoothManager mgr = mAdapter.getBluetoothManager();
300        if (mgr != null) {
301            try {
302                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
303            } catch (Exception e) {
304                Log.e(TAG,"",e);
305            }
306        }
307
308        synchronized (mConnection) {
309            if (mService != null) {
310                try {
311                    mService = null;
312                    mContext.unbindService(mConnection);
313                } catch (Exception re) {
314                    Log.e(TAG,"",re);
315                }
316            }
317        }
318        mServiceListener = null;
319    }
320
321    /**
322     * Initiate connection to a profile of the remote bluetooth device.
323     *
324     * <p> Currently, the system supports only 1 connection to the
325     * headset/handsfree profile. The API will automatically disconnect connected
326     * devices before connecting.
327     *
328     * <p> This API returns false in scenarios like the profile on the
329     * device is already connected or Bluetooth is not turned on.
330     * When this API returns true, it is guaranteed that
331     * connection state intent for the profile will be broadcasted with
332     * the state. Users can get the connection state of the profile
333     * from this intent.
334     *
335     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
336     * permission.
337     *
338     * @param device Remote Bluetooth Device
339     * @return false on immediate error,
340     *               true otherwise
341     * @hide
342     */
343    public boolean connect(BluetoothDevice device) {
344        if (DBG) log("connect(" + device + ")");
345        if (mService != null && isEnabled() &&
346            isValidDevice(device)) {
347            try {
348                return mService.connect(device);
349            } catch (RemoteException e) {
350                Log.e(TAG, Log.getStackTraceString(new Throwable()));
351                return false;
352            }
353        }
354        if (mService == null) Log.w(TAG, "Proxy not attached to service");
355        return false;
356    }
357
358    /**
359     * Initiate disconnection from a profile
360     *
361     * <p> This API will return false in scenarios like the profile on the
362     * Bluetooth device is not in connected state etc. When this API returns,
363     * true, it is guaranteed that the connection state change
364     * intent will be broadcasted with the state. Users can get the
365     * disconnection state of the profile from this intent.
366     *
367     * <p> If the disconnection is initiated by a remote device, the state
368     * will transition from {@link #STATE_CONNECTED} to
369     * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
370     * host (local) device the state will transition from
371     * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
372     * state {@link #STATE_DISCONNECTED}. The transition to
373     * {@link #STATE_DISCONNECTING} can be used to distinguish between the
374     * two scenarios.
375     *
376     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
377     * permission.
378     *
379     * @param device Remote Bluetooth Device
380     * @return false on immediate error,
381     *               true otherwise
382     * @hide
383     */
384    public boolean disconnect(BluetoothDevice device) {
385        if (DBG) log("disconnect(" + device + ")");
386        if (mService != null && isEnabled() &&
387            isValidDevice(device)) {
388            try {
389                return mService.disconnect(device);
390            } catch (RemoteException e) {
391              Log.e(TAG, Log.getStackTraceString(new Throwable()));
392              return false;
393            }
394        }
395        if (mService == null) Log.w(TAG, "Proxy not attached to service");
396        return false;
397    }
398
399    /**
400     * {@inheritDoc}
401     */
402    public List<BluetoothDevice> getConnectedDevices() {
403        if (VDBG) log("getConnectedDevices()");
404        if (mService != null && isEnabled()) {
405            try {
406                return mService.getConnectedDevices();
407            } catch (RemoteException e) {
408                Log.e(TAG, Log.getStackTraceString(new Throwable()));
409                return new ArrayList<BluetoothDevice>();
410            }
411        }
412        if (mService == null) Log.w(TAG, "Proxy not attached to service");
413        return new ArrayList<BluetoothDevice>();
414    }
415
416    /**
417     * {@inheritDoc}
418     */
419    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
420        if (VDBG) log("getDevicesMatchingStates()");
421        if (mService != null && isEnabled()) {
422            try {
423                return mService.getDevicesMatchingConnectionStates(states);
424            } catch (RemoteException e) {
425                Log.e(TAG, Log.getStackTraceString(new Throwable()));
426                return new ArrayList<BluetoothDevice>();
427            }
428        }
429        if (mService == null) Log.w(TAG, "Proxy not attached to service");
430        return new ArrayList<BluetoothDevice>();
431    }
432
433    /**
434     * {@inheritDoc}
435     */
436    public int getConnectionState(BluetoothDevice device) {
437        if (VDBG) log("getConnectionState(" + device + ")");
438        if (mService != null && isEnabled() &&
439            isValidDevice(device)) {
440            try {
441                return mService.getConnectionState(device);
442            } catch (RemoteException e) {
443                Log.e(TAG, Log.getStackTraceString(new Throwable()));
444                return BluetoothProfile.STATE_DISCONNECTED;
445            }
446        }
447        if (mService == null) Log.w(TAG, "Proxy not attached to service");
448        return BluetoothProfile.STATE_DISCONNECTED;
449    }
450
451    /**
452     * Set priority of the profile
453     *
454     * <p> The device should already be paired.
455     *  Priority can be one of {@link #PRIORITY_ON} or
456     * {@link #PRIORITY_OFF},
457     *
458     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
459     * permission.
460     *
461     * @param device Paired bluetooth device
462     * @param priority
463     * @return true if priority is set, false on error
464     * @hide
465     */
466    public boolean setPriority(BluetoothDevice device, int priority) {
467        if (DBG) log("setPriority(" + device + ", " + priority + ")");
468        if (mService != null && isEnabled() &&
469            isValidDevice(device)) {
470            if (priority != BluetoothProfile.PRIORITY_OFF &&
471                priority != BluetoothProfile.PRIORITY_ON) {
472              return false;
473            }
474            try {
475                return mService.setPriority(device, priority);
476            } catch (RemoteException e) {
477                Log.e(TAG, Log.getStackTraceString(new Throwable()));
478                return false;
479            }
480        }
481        if (mService == null) Log.w(TAG, "Proxy not attached to service");
482        return false;
483    }
484
485    /**
486     * Get the priority of the profile.
487     *
488     * <p> The priority can be any of:
489     * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
490     * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
491     *
492     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
493     *
494     * @param device Bluetooth device
495     * @return priority of the device
496     * @hide
497     */
498    public int getPriority(BluetoothDevice device) {
499        if (VDBG) log("getPriority(" + device + ")");
500        if (mService != null && isEnabled() &&
501            isValidDevice(device)) {
502            try {
503                return mService.getPriority(device);
504            } catch (RemoteException e) {
505                Log.e(TAG, Log.getStackTraceString(new Throwable()));
506                return PRIORITY_OFF;
507            }
508        }
509        if (mService == null) Log.w(TAG, "Proxy not attached to service");
510        return PRIORITY_OFF;
511    }
512
513    /**
514     * Start Bluetooth voice recognition. This methods sends the voice
515     * recognition AT command to the headset and establishes the
516     * audio connection.
517     *
518     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
519     * If this function returns true, this intent will be broadcasted with
520     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
521     *
522     * <p> {@link #EXTRA_STATE} will transition from
523     * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
524     * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
525     * in case of failure to establish the audio connection.
526     *
527     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
528     *
529     * @param device Bluetooth headset
530     * @return false if there is no headset connected of if the
531     *               connected headset doesn't support voice recognition
532     *               or on error, true otherwise
533     */
534    public boolean startVoiceRecognition(BluetoothDevice device) {
535        if (DBG) log("startVoiceRecognition()");
536        if (mService != null && isEnabled() &&
537            isValidDevice(device)) {
538            try {
539                return mService.startVoiceRecognition(device);
540            } catch (RemoteException e) {
541                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
542            }
543        }
544        if (mService == null) Log.w(TAG, "Proxy not attached to service");
545        return false;
546    }
547
548    /**
549     * Stop Bluetooth Voice Recognition mode, and shut down the
550     * Bluetooth audio path.
551     *
552     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
553     *
554     * @param device Bluetooth headset
555     * @return false if there is no headset connected
556     *               or on error, true otherwise
557     */
558    public boolean stopVoiceRecognition(BluetoothDevice device) {
559        if (DBG) log("stopVoiceRecognition()");
560        if (mService != null && isEnabled() &&
561            isValidDevice(device)) {
562            try {
563                return mService.stopVoiceRecognition(device);
564            } catch (RemoteException e) {
565                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
566            }
567        }
568        if (mService == null) Log.w(TAG, "Proxy not attached to service");
569        return false;
570    }
571
572    /**
573     * Check if Bluetooth SCO audio is connected.
574     *
575     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
576     *
577     * @param device Bluetooth headset
578     * @return true if SCO is connected,
579     *         false otherwise or on error
580     */
581    public boolean isAudioConnected(BluetoothDevice device) {
582        if (VDBG) log("isAudioConnected()");
583        if (mService != null && isEnabled() &&
584            isValidDevice(device)) {
585            try {
586              return mService.isAudioConnected(device);
587            } catch (RemoteException e) {
588              Log.e(TAG,  Log.getStackTraceString(new Throwable()));
589            }
590        }
591        if (mService == null) Log.w(TAG, "Proxy not attached to service");
592        return false;
593    }
594
595    /**
596     * Get battery usage hint for Bluetooth Headset service.
597     * This is a monotonically increasing integer. Wraps to 0 at
598     * Integer.MAX_INT, and at boot.
599     * Current implementation returns the number of AT commands handled since
600     * boot. This is a good indicator for spammy headset/handsfree units that
601     * can keep the device awake by polling for cellular status updates. As a
602     * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
603     *
604     * @param device the bluetooth headset.
605     * @return monotonically increasing battery usage hint, or a negative error
606     *         code on error
607     * @hide
608     */
609    public int getBatteryUsageHint(BluetoothDevice device) {
610        if (VDBG) log("getBatteryUsageHint()");
611        if (mService != null && isEnabled() &&
612            isValidDevice(device)) {
613            try {
614                return mService.getBatteryUsageHint(device);
615            } catch (RemoteException e) {
616                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
617            }
618        }
619        if (mService == null) Log.w(TAG, "Proxy not attached to service");
620        return -1;
621    }
622
623    /**
624     * Indicates if current platform supports voice dialing over bluetooth SCO.
625     *
626     * @return true if voice dialing over bluetooth is supported, false otherwise.
627     * @hide
628     */
629    public static boolean isBluetoothVoiceDialingEnabled(Context context) {
630        return context.getResources().getBoolean(
631                com.android.internal.R.bool.config_bluetooth_sco_off_call);
632    }
633
634    /**
635     * Accept the incoming connection.
636     * Note: This is an internal function and shouldn't be exposed
637     *
638     * @hide
639     */
640    public boolean acceptIncomingConnect(BluetoothDevice device) {
641        if (DBG) log("acceptIncomingConnect");
642        if (mService != null && isEnabled()) {
643            try {
644                return mService.acceptIncomingConnect(device);
645            } catch (RemoteException e) {Log.e(TAG, e.toString());}
646        } else {
647            Log.w(TAG, "Proxy not attached to service");
648            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
649        }
650        return false;
651    }
652
653    /**
654     * Reject the incoming connection.
655     * @hide
656     */
657    public boolean rejectIncomingConnect(BluetoothDevice device) {
658        if (DBG) log("rejectIncomingConnect");
659        if (mService != null) {
660            try {
661                return mService.rejectIncomingConnect(device);
662            } catch (RemoteException e) {Log.e(TAG, e.toString());}
663        } else {
664            Log.w(TAG, "Proxy not attached to service");
665            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
666        }
667        return false;
668    }
669
670    /**
671     * Get the current audio state of the Headset.
672     * Note: This is an internal function and shouldn't be exposed
673     *
674     * @hide
675     */
676    public int getAudioState(BluetoothDevice device) {
677        if (VDBG) log("getAudioState");
678        if (mService != null && !isDisabled()) {
679            try {
680                return mService.getAudioState(device);
681            } catch (RemoteException e) {Log.e(TAG, e.toString());}
682        } else {
683            Log.w(TAG, "Proxy not attached to service");
684            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
685        }
686        return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
687    }
688
689    /**
690     * Check if Bluetooth SCO audio is connected.
691     *
692     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
693     *
694     * @return true if SCO is connected,
695     *         false otherwise or on error
696     * @hide
697     */
698    public boolean isAudioOn() {
699        if (VDBG) log("isAudioOn()");
700        if (mService != null && isEnabled()) {
701            try {
702              return mService.isAudioOn();
703            } catch (RemoteException e) {
704              Log.e(TAG,  Log.getStackTraceString(new Throwable()));
705            }
706        }
707        if (mService == null) Log.w(TAG, "Proxy not attached to service");
708        return false;
709
710    }
711
712    /**
713     * Initiates a connection of headset audio.
714     * It setup SCO channel with remote connected headset device.
715     *
716     * @return true if successful
717     *         false if there was some error such as
718     *               there is no connected headset
719     * @hide
720     */
721    public boolean connectAudio() {
722        if (mService != null && isEnabled()) {
723            try {
724                return mService.connectAudio();
725            } catch (RemoteException e) {
726                Log.e(TAG, e.toString());
727            }
728        } else {
729            Log.w(TAG, "Proxy not attached to service");
730            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
731        }
732        return false;
733    }
734
735    /**
736     * Initiates a disconnection of headset audio.
737     * It tears down the SCO channel from remote headset device.
738     *
739     * @return true if successful
740     *         false if there was some error such as
741     *               there is no connected SCO channel
742     * @hide
743     */
744    public boolean disconnectAudio() {
745        if (mService != null && isEnabled()) {
746            try {
747                return mService.disconnectAudio();
748            } catch (RemoteException e) {
749                Log.e(TAG, e.toString());
750            }
751        } else {
752            Log.w(TAG, "Proxy not attached to service");
753            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
754        }
755        return false;
756    }
757
758    /**
759     * Initiates a SCO channel connection with the headset (if connected).
760     * Also initiates a virtual voice call for Handsfree devices as many devices
761     * do not accept SCO audio without a call.
762     * This API allows the handsfree device to be used for routing non-cellular
763     * call audio.
764     *
765     * @param device Remote Bluetooth Device
766     * @return true if successful, false if there was some error.
767     * @hide
768     */
769    public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
770        if (DBG) log("startScoUsingVirtualVoiceCall()");
771        if (mService != null && isEnabled() && isValidDevice(device)) {
772            try {
773                return mService.startScoUsingVirtualVoiceCall(device);
774            } catch (RemoteException e) {
775                Log.e(TAG, e.toString());
776            }
777        } else {
778            Log.w(TAG, "Proxy not attached to service");
779            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
780        }
781        return false;
782    }
783
784    /**
785     * Terminates an ongoing SCO connection and the associated virtual
786     * call.
787     *
788     * @param device Remote Bluetooth Device
789     * @return true if successful, false if there was some error.
790     * @hide
791     */
792    public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
793        if (DBG) log("stopScoUsingVirtualVoiceCall()");
794        if (mService != null && isEnabled() && isValidDevice(device)) {
795            try {
796                return mService.stopScoUsingVirtualVoiceCall(device);
797            } catch (RemoteException e) {
798                Log.e(TAG, e.toString());
799            }
800        } else {
801            Log.w(TAG, "Proxy not attached to service");
802            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
803        }
804        return false;
805    }
806
807    /**
808     * Notify Headset of phone state change.
809     * This is a backdoor for phone app to call BluetoothHeadset since
810     * there is currently not a good way to get precise call state change outside
811     * of phone app.
812     *
813     * @hide
814     */
815    public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
816                                  int type) {
817        if (mService != null && isEnabled()) {
818            try {
819                mService.phoneStateChanged(numActive, numHeld, callState, number, type);
820            } catch (RemoteException e) {
821                Log.e(TAG, e.toString());
822            }
823        } else {
824            Log.w(TAG, "Proxy not attached to service");
825            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
826        }
827    }
828
829    /**
830     * Send Headset of CLCC response
831     *
832     * @hide
833     */
834    public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
835                             String number, int type) {
836        if (mService != null && isEnabled()) {
837            try {
838                mService.clccResponse(index, direction, status, mode, mpty, number, type);
839            } catch (RemoteException e) {
840                Log.e(TAG, e.toString());
841            }
842        } else {
843            Log.w(TAG, "Proxy not attached to service");
844            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
845        }
846    }
847
848    /**
849     * Sends a vendor-specific unsolicited result code to the headset.
850     *
851     * <p>The actual string to be sent is <code>command + ": " + arg</code>.
852     * For example, if {@code command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg}
853     * is {@code "0"}, the string <code>"+ANDROID: 0"</code> will be sent.
854     *
855     * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}.
856     *
857     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
858     *
859     * @param device Bluetooth headset.
860     * @param command A vendor-specific command.
861     * @param arg The argument that will be attached to the command.
862     * @return {@code false} if there is no headset connected, or if the command is not an allowed
863     *         vendor-specific unsolicited result code, or on error. {@code true} otherwise.
864     * @throws IllegalArgumentException if {@code command} is {@code null}.
865     */
866    public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
867            String arg) {
868        if (DBG) {
869            log("sendVendorSpecificResultCode()");
870        }
871        if (command == null) {
872            throw new IllegalArgumentException("command is null");
873        }
874        if (mService != null && isEnabled() &&
875                isValidDevice(device)) {
876            try {
877                return mService.sendVendorSpecificResultCode(device, command, arg);
878            } catch (RemoteException e) {
879                Log.e(TAG, Log.getStackTraceString(new Throwable()));
880            }
881        }
882        if (mService == null) {
883            Log.w(TAG, "Proxy not attached to service");
884        }
885        return false;
886    }
887
888    private final ServiceConnection mConnection = new ServiceConnection() {
889        public void onServiceConnected(ComponentName className, IBinder service) {
890            if (DBG) Log.d(TAG, "Proxy object connected");
891            mService = IBluetoothHeadset.Stub.asInterface(service);
892
893            if (mServiceListener != null) {
894                mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, BluetoothHeadset.this);
895            }
896        }
897        public void onServiceDisconnected(ComponentName className) {
898            if (DBG) Log.d(TAG, "Proxy object disconnected");
899            mService = null;
900            if (mServiceListener != null) {
901                mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
902            }
903        }
904    };
905
906    private boolean isEnabled() {
907       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
908       return false;
909    }
910
911    private boolean isDisabled() {
912       if (mAdapter.getState() == BluetoothAdapter.STATE_OFF) return true;
913       return false;
914    }
915
916    private boolean isValidDevice(BluetoothDevice device) {
917       if (device == null) return false;
918
919       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
920       return false;
921    }
922
923    private static void log(String msg) {
924        Log.d(TAG, msg);
925    }
926}
927