BluetoothHeadset.java revision 0f42037eb7b5118015c2caca635538324ccf0ccf
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
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    private BluetoothAdapter mAdapter;
223
224    final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
225            new IBluetoothStateChangeCallback.Stub() {
226                public void onBluetoothStateChange(boolean up) {
227                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
228                    if (!up) {
229                        if (DBG) Log.d(TAG,"Unbinding service...");
230                        synchronized (mConnection) {
231                            try {
232                                mService = null;
233                                mContext.unbindService(mConnection);
234                            } catch (Exception re) {
235                                Log.e(TAG,"",re);
236                            }
237                        }
238                    } else {
239                        synchronized (mConnection) {
240                            try {
241                                if (mService == null) {
242                                    if (DBG) Log.d(TAG,"Binding service...");
243                                    if (!mContext.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) {
244                                        Log.e(TAG, "Could not bind to Bluetooth Headset Service");
245                                    }
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        if (!context.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) {
273            Log.e(TAG, "Could not bind to Bluetooth Headset Service");
274        }
275    }
276
277    /**
278     * Close the connection to the backing service.
279     * Other public functions of BluetoothHeadset will return default error
280     * results once close() has been called. Multiple invocations of close()
281     * are ok.
282     */
283    /*package*/ void close() {
284        if (DBG) log("close()");
285
286        IBluetoothManager mgr = mAdapter.getBluetoothManager();
287        if (mgr != null) {
288            try {
289                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
290            } catch (Exception e) {
291                Log.e(TAG,"",e);
292            }
293        }
294
295        synchronized (mConnection) {
296            if (mService != null) {
297                try {
298                    mService = null;
299                    mContext.unbindService(mConnection);
300                } catch (Exception re) {
301                    Log.e(TAG,"",re);
302                }
303            }
304        }
305        mServiceListener = null;
306    }
307
308    /**
309     * Initiate connection to a profile of the remote bluetooth device.
310     *
311     * <p> Currently, the system supports only 1 connection to the
312     * headset/handsfree profile. The API will automatically disconnect connected
313     * devices before connecting.
314     *
315     * <p> This API returns false in scenarios like the profile on the
316     * device is already connected or Bluetooth is not turned on.
317     * When this API returns true, it is guaranteed that
318     * connection state intent for the profile will be broadcasted with
319     * the state. Users can get the connection state of the profile
320     * from this intent.
321     *
322     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
323     * permission.
324     *
325     * @param device Remote Bluetooth Device
326     * @return false on immediate error,
327     *               true otherwise
328     * @hide
329     */
330    public boolean connect(BluetoothDevice device) {
331        if (DBG) log("connect(" + device + ")");
332        if (mService != null && isEnabled() &&
333            isValidDevice(device)) {
334            try {
335                return mService.connect(device);
336            } catch (RemoteException e) {
337                Log.e(TAG, Log.getStackTraceString(new Throwable()));
338                return false;
339            }
340        }
341        if (mService == null) Log.w(TAG, "Proxy not attached to service");
342        return false;
343    }
344
345    /**
346     * Initiate disconnection from a profile
347     *
348     * <p> This API will return false in scenarios like the profile on the
349     * Bluetooth device is not in connected state etc. When this API returns,
350     * true, it is guaranteed that the connection state change
351     * intent will be broadcasted with the state. Users can get the
352     * disconnection state of the profile from this intent.
353     *
354     * <p> If the disconnection is initiated by a remote device, the state
355     * will transition from {@link #STATE_CONNECTED} to
356     * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
357     * host (local) device the state will transition from
358     * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
359     * state {@link #STATE_DISCONNECTED}. The transition to
360     * {@link #STATE_DISCONNECTING} can be used to distinguish between the
361     * two scenarios.
362     *
363     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
364     * permission.
365     *
366     * @param device Remote Bluetooth Device
367     * @return false on immediate error,
368     *               true otherwise
369     * @hide
370     */
371    public boolean disconnect(BluetoothDevice device) {
372        if (DBG) log("disconnect(" + device + ")");
373        if (mService != null && isEnabled() &&
374            isValidDevice(device)) {
375            try {
376                return mService.disconnect(device);
377            } catch (RemoteException e) {
378              Log.e(TAG, Log.getStackTraceString(new Throwable()));
379              return false;
380            }
381        }
382        if (mService == null) Log.w(TAG, "Proxy not attached to service");
383        return false;
384    }
385
386    /**
387     * {@inheritDoc}
388     */
389    public List<BluetoothDevice> getConnectedDevices() {
390        if (DBG) log("getConnectedDevices()");
391        if (mService != null && isEnabled()) {
392            try {
393                return mService.getConnectedDevices();
394            } catch (RemoteException e) {
395                Log.e(TAG, Log.getStackTraceString(new Throwable()));
396                return new ArrayList<BluetoothDevice>();
397            }
398        }
399        if (mService == null) Log.w(TAG, "Proxy not attached to service");
400        return new ArrayList<BluetoothDevice>();
401    }
402
403    /**
404     * {@inheritDoc}
405     */
406    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
407        if (DBG) log("getDevicesMatchingStates()");
408        if (mService != null && isEnabled()) {
409            try {
410                return mService.getDevicesMatchingConnectionStates(states);
411            } catch (RemoteException e) {
412                Log.e(TAG, Log.getStackTraceString(new Throwable()));
413                return new ArrayList<BluetoothDevice>();
414            }
415        }
416        if (mService == null) Log.w(TAG, "Proxy not attached to service");
417        return new ArrayList<BluetoothDevice>();
418    }
419
420    /**
421     * {@inheritDoc}
422     */
423    public int getConnectionState(BluetoothDevice device) {
424        if (DBG) log("getConnectionState(" + device + ")");
425        if (mService != null && isEnabled() &&
426            isValidDevice(device)) {
427            try {
428                return mService.getConnectionState(device);
429            } catch (RemoteException e) {
430                Log.e(TAG, Log.getStackTraceString(new Throwable()));
431                return BluetoothProfile.STATE_DISCONNECTED;
432            }
433        }
434        if (mService == null) Log.w(TAG, "Proxy not attached to service");
435        return BluetoothProfile.STATE_DISCONNECTED;
436    }
437
438    /**
439     * Set priority of the profile
440     *
441     * <p> The device should already be paired.
442     *  Priority can be one of {@link #PRIORITY_ON} or
443     * {@link #PRIORITY_OFF},
444     *
445     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
446     * permission.
447     *
448     * @param device Paired bluetooth device
449     * @param priority
450     * @return true if priority is set, false on error
451     * @hide
452     */
453    public boolean setPriority(BluetoothDevice device, int priority) {
454        if (DBG) log("setPriority(" + device + ", " + priority + ")");
455        if (mService != null && isEnabled() &&
456            isValidDevice(device)) {
457            if (priority != BluetoothProfile.PRIORITY_OFF &&
458                priority != BluetoothProfile.PRIORITY_ON) {
459              return false;
460            }
461            try {
462                return mService.setPriority(device, priority);
463            } catch (RemoteException e) {
464                Log.e(TAG, Log.getStackTraceString(new Throwable()));
465                return false;
466            }
467        }
468        if (mService == null) Log.w(TAG, "Proxy not attached to service");
469        return false;
470    }
471
472    /**
473     * Get the priority of the profile.
474     *
475     * <p> The priority can be any of:
476     * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
477     * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
478     *
479     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
480     *
481     * @param device Bluetooth device
482     * @return priority of the device
483     * @hide
484     */
485    public int getPriority(BluetoothDevice device) {
486        if (DBG) log("getPriority(" + device + ")");
487        if (mService != null && isEnabled() &&
488            isValidDevice(device)) {
489            try {
490                return mService.getPriority(device);
491            } catch (RemoteException e) {
492                Log.e(TAG, Log.getStackTraceString(new Throwable()));
493                return PRIORITY_OFF;
494            }
495        }
496        if (mService == null) Log.w(TAG, "Proxy not attached to service");
497        return PRIORITY_OFF;
498    }
499
500    /**
501     * Start Bluetooth voice recognition. This methods sends the voice
502     * recognition AT command to the headset and establishes the
503     * audio connection.
504     *
505     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
506     * If this function returns true, this intent will be broadcasted with
507     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
508     *
509     * <p> {@link #EXTRA_STATE} will transition from
510     * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
511     * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
512     * in case of failure to establish the audio connection.
513     *
514     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
515     *
516     * @param device Bluetooth headset
517     * @return false if there is no headset connected of if the
518     *               connected headset doesn't support voice recognition
519     *               or on error, true otherwise
520     */
521    public boolean startVoiceRecognition(BluetoothDevice device) {
522        if (DBG) log("startVoiceRecognition()");
523        if (mService != null && isEnabled() &&
524            isValidDevice(device)) {
525            try {
526                return mService.startVoiceRecognition(device);
527            } catch (RemoteException e) {
528                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
529            }
530        }
531        if (mService == null) Log.w(TAG, "Proxy not attached to service");
532        return false;
533    }
534
535    /**
536     * Stop Bluetooth Voice Recognition mode, and shut down the
537     * Bluetooth audio path.
538     *
539     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
540     *
541     * @param device Bluetooth headset
542     * @return false if there is no headset connected
543     *               or on error, true otherwise
544     */
545    public boolean stopVoiceRecognition(BluetoothDevice device) {
546        if (DBG) log("stopVoiceRecognition()");
547        if (mService != null && isEnabled() &&
548            isValidDevice(device)) {
549            try {
550                return mService.stopVoiceRecognition(device);
551            } catch (RemoteException e) {
552                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
553            }
554        }
555        if (mService == null) Log.w(TAG, "Proxy not attached to service");
556        return false;
557    }
558
559    /**
560     * Check if Bluetooth SCO audio is connected.
561     *
562     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
563     *
564     * @param device Bluetooth headset
565     * @return true if SCO is connected,
566     *         false otherwise or on error
567     */
568    public boolean isAudioConnected(BluetoothDevice device) {
569        if (DBG) log("isAudioConnected()");
570        if (mService != null && isEnabled() &&
571            isValidDevice(device)) {
572            try {
573              return mService.isAudioConnected(device);
574            } catch (RemoteException e) {
575              Log.e(TAG,  Log.getStackTraceString(new Throwable()));
576            }
577        }
578        if (mService == null) Log.w(TAG, "Proxy not attached to service");
579        return false;
580    }
581
582    /**
583     * Get battery usage hint for Bluetooth Headset service.
584     * This is a monotonically increasing integer. Wraps to 0 at
585     * Integer.MAX_INT, and at boot.
586     * Current implementation returns the number of AT commands handled since
587     * boot. This is a good indicator for spammy headset/handsfree units that
588     * can keep the device awake by polling for cellular status updates. As a
589     * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
590     *
591     * @param device the bluetooth headset.
592     * @return monotonically increasing battery usage hint, or a negative error
593     *         code on error
594     * @hide
595     */
596    public int getBatteryUsageHint(BluetoothDevice device) {
597        if (DBG) log("getBatteryUsageHint()");
598        if (mService != null && isEnabled() &&
599            isValidDevice(device)) {
600            try {
601                return mService.getBatteryUsageHint(device);
602            } catch (RemoteException e) {
603                Log.e(TAG,  Log.getStackTraceString(new Throwable()));
604            }
605        }
606        if (mService == null) Log.w(TAG, "Proxy not attached to service");
607        return -1;
608    }
609
610    /**
611     * Indicates if current platform supports voice dialing over bluetooth SCO.
612     *
613     * @return true if voice dialing over bluetooth is supported, false otherwise.
614     * @hide
615     */
616    public static boolean isBluetoothVoiceDialingEnabled(Context context) {
617        return context.getResources().getBoolean(
618                com.android.internal.R.bool.config_bluetooth_sco_off_call);
619    }
620
621    /**
622     * Accept the incoming connection.
623     * Note: This is an internal function and shouldn't be exposed
624     *
625     * @hide
626     */
627    public boolean acceptIncomingConnect(BluetoothDevice device) {
628        if (DBG) log("acceptIncomingConnect");
629        if (mService != null && isEnabled()) {
630            try {
631                return mService.acceptIncomingConnect(device);
632            } catch (RemoteException e) {Log.e(TAG, e.toString());}
633        } else {
634            Log.w(TAG, "Proxy not attached to service");
635            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
636        }
637        return false;
638    }
639
640    /**
641     * Reject the incoming connection.
642     * @hide
643     */
644    public boolean rejectIncomingConnect(BluetoothDevice device) {
645        if (DBG) log("rejectIncomingConnect");
646        if (mService != null) {
647            try {
648                return mService.rejectIncomingConnect(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     * Get the current audio state of the Headset.
659     * Note: This is an internal function and shouldn't be exposed
660     *
661     * @hide
662     */
663    public int getAudioState(BluetoothDevice device) {
664        if (DBG) log("getAudioState");
665        if (mService != null && !isDisabled()) {
666            try {
667                return mService.getAudioState(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 BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
674    }
675
676    /**
677     * Check if Bluetooth SCO audio is connected.
678     *
679     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
680     *
681     * @return true if SCO is connected,
682     *         false otherwise or on error
683     * @hide
684     */
685    public boolean isAudioOn() {
686        if (DBG) log("isAudioOn()");
687        if (mService != null && isEnabled()) {
688            try {
689              return mService.isAudioOn();
690            } catch (RemoteException e) {
691              Log.e(TAG,  Log.getStackTraceString(new Throwable()));
692            }
693        }
694        if (mService == null) Log.w(TAG, "Proxy not attached to service");
695        return false;
696
697    }
698
699    /**
700     * Initiates a connection of headset audio.
701     * It setup SCO channel with remote connected headset device.
702     *
703     * @return true if successful
704     *         false if there was some error such as
705     *               there is no connected headset
706     * @hide
707     */
708    public boolean connectAudio() {
709        if (mService != null && isEnabled()) {
710            try {
711                return mService.connectAudio();
712            } catch (RemoteException e) {
713                Log.e(TAG, e.toString());
714            }
715        } else {
716            Log.w(TAG, "Proxy not attached to service");
717            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
718        }
719        return false;
720    }
721
722    /**
723     * Initiates a disconnection of headset audio.
724     * It tears down the SCO channel from remote headset device.
725     *
726     * @return true if successful
727     *         false if there was some error such as
728     *               there is no connected SCO channel
729     * @hide
730     */
731    public boolean disconnectAudio() {
732        if (mService != null && isEnabled()) {
733            try {
734                return mService.disconnectAudio();
735            } catch (RemoteException e) {
736                Log.e(TAG, e.toString());
737            }
738        } else {
739            Log.w(TAG, "Proxy not attached to service");
740            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
741        }
742        return false;
743    }
744
745    /**
746     * Initiates a SCO channel connection with the headset (if connected).
747     * Also initiates a virtual voice call for Handsfree devices as many devices
748     * do not accept SCO audio without a call.
749     * This API allows the handsfree device to be used for routing non-cellular
750     * call audio.
751     *
752     * @param device Remote Bluetooth Device
753     * @return true if successful, false if there was some error.
754     * @hide
755     */
756    public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
757        if (DBG) log("startScoUsingVirtualVoiceCall()");
758        if (mService != null && isEnabled() && isValidDevice(device)) {
759            try {
760                return mService.startScoUsingVirtualVoiceCall(device);
761            } catch (RemoteException e) {
762                Log.e(TAG, e.toString());
763            }
764        } else {
765            Log.w(TAG, "Proxy not attached to service");
766            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
767        }
768        return false;
769    }
770
771    /**
772     * Terminates an ongoing SCO connection and the associated virtual
773     * call.
774     *
775     * @param device Remote Bluetooth Device
776     * @return true if successful, false if there was some error.
777     * @hide
778     */
779    public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
780        if (DBG) log("stopScoUsingVirtualVoiceCall()");
781        if (mService != null && isEnabled() && isValidDevice(device)) {
782            try {
783                return mService.stopScoUsingVirtualVoiceCall(device);
784            } catch (RemoteException e) {
785                Log.e(TAG, e.toString());
786            }
787        } else {
788            Log.w(TAG, "Proxy not attached to service");
789            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
790        }
791        return false;
792    }
793
794    /**
795     * Notify Headset of phone state change.
796     * This is a backdoor for phone app to call BluetoothHeadset since
797     * there is currently not a good way to get precise call state change outside
798     * of phone app.
799     *
800     * @hide
801     */
802    public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
803                                  int type) {
804        if (mService != null && isEnabled()) {
805            try {
806                mService.phoneStateChanged(numActive, numHeld, callState, number, type);
807            } catch (RemoteException e) {
808                Log.e(TAG, e.toString());
809            }
810        } else {
811            Log.w(TAG, "Proxy not attached to service");
812            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
813        }
814    }
815
816    /**
817     * Notify Headset of phone roam state change.
818     * This is a backdoor for phone app to call BluetoothHeadset since
819     * there is currently not a good way to get roaming state change outside
820     * of phone app.
821     *
822     * @hide
823     */
824    public void roamChanged(boolean roaming) {
825        if (mService != null && isEnabled()) {
826            try {
827                mService.roamChanged(roaming);
828            } catch (RemoteException e) {
829                Log.e(TAG, e.toString());
830            }
831        } else {
832            Log.w(TAG, "Proxy not attached to service");
833            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
834        }
835    }
836
837    /**
838     * Send Headset of CLCC response
839     *
840     * @hide
841     */
842    public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
843                             String number, int type) {
844        if (mService != null && isEnabled()) {
845            try {
846                mService.clccResponse(index, direction, status, mode, mpty, number, type);
847            } catch (RemoteException e) {
848                Log.e(TAG, e.toString());
849            }
850        } else {
851            Log.w(TAG, "Proxy not attached to service");
852            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
853        }
854    }
855
856    private ServiceConnection mConnection = new ServiceConnection() {
857        public void onServiceConnected(ComponentName className, IBinder service) {
858            if (DBG) Log.d(TAG, "Proxy object connected");
859            mService = IBluetoothHeadset.Stub.asInterface(service);
860
861            if (mServiceListener != null) {
862                mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, BluetoothHeadset.this);
863            }
864        }
865        public void onServiceDisconnected(ComponentName className) {
866            if (DBG) Log.d(TAG, "Proxy object disconnected");
867            mService = null;
868            if (mServiceListener != null) {
869                mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
870            }
871        }
872    };
873
874    private boolean isEnabled() {
875       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
876       return false;
877    }
878
879    private boolean isDisabled() {
880       if (mAdapter.getState() == BluetoothAdapter.STATE_OFF) return true;
881       return false;
882    }
883
884    private boolean isValidDevice(BluetoothDevice device) {
885       if (device == null) return false;
886
887       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
888       return false;
889    }
890
891    private static void log(String msg) {
892        Log.d(TAG, msg);
893    }
894}
895