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