• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.bluetooth;
18 
19 import android.annotation.SdkConstant;
20 import android.annotation.SdkConstant.SdkConstantType;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.os.Handler;
24 import android.os.IBinder;
25 import android.os.Looper;
26 import android.os.Message;
27 import android.os.RemoteException;
28 import android.util.Log;
29 
30 import java.util.ArrayList;
31 import 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  */
47 public 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 
224     /**
225      * Intent used to broadcast the headset's indicator status
226      *
227      * <p>This intent will have 3 extras:
228      * <ul>
229      *   <li> {@link #EXTRA_IND_ID} - The Assigned number of headset Indicator which is supported by
230                                         the headset ( as indicated by AT+BIND
231                                         command in the SLC sequence).or whose value
232                                         is changed (indicated by AT+BIEV command)</li>
233      *   <li> {@link #EXTRA_IND_VALUE}- The updated value of headset indicator. </li>
234      *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
235      * </ul>
236      * <p>{@link #EXTRA_IND_ID} is defined by Bluetooth SIG and each of the indicators are
237      * given an assigned number. Below shows the assigned number of Indicator added so far
238      * - Enhanced Safety - 1
239      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
240      * receive.
241      * @hide
242      */
243     public static final String ACTION_HF_INDICATORS_VALUE_CHANGED =
244             "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED";
245 
246     /**
247      * A String extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
248      * intents that contains the UUID of the headset  indicator (as defined by Bluetooth SIG)
249      * that is being sent.
250      * @hide
251      */
252     public static final String EXTRA_HF_INDICATORS_IND_ID =
253             "android.bluetooth.headset.extra.HF_INDICATORS_IND_ID";
254 
255     /**
256      * A int  extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
257      * intents that contains the value of the Headset indicator that is being sent.
258      * @hide
259      */
260     public static final String EXTRA_HF_INDICATORS_IND_VALUE =
261             "android.bluetooth.headset.extra.HF_INDICATORS_IND_VALUE";
262 
263     public static final int STATE_AUDIO_CONNECTED = 12;
264 
265     private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100;
266     private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101;
267 
268     private Context mContext;
269     private ServiceListener mServiceListener;
270     private IBluetoothHeadset mService;
271     private BluetoothAdapter mAdapter;
272 
273     final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
274             new IBluetoothStateChangeCallback.Stub() {
275                 public void onBluetoothStateChange(boolean up) {
276                     if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
277                     if (!up) {
278                         if (VDBG) Log.d(TAG,"Unbinding service...");
279                         doUnbind();
280                     } else {
281                         synchronized (mConnection) {
282                             try {
283                                 if (mService == null) {
284                                     if (VDBG) Log.d(TAG,"Binding service...");
285                                     doBind();
286                                 }
287                             } catch (Exception re) {
288                                 Log.e(TAG,"",re);
289                             }
290                         }
291                     }
292                 }
293         };
294 
295     /**
296      * Create a BluetoothHeadset proxy object.
297      */
BluetoothHeadset(Context context, ServiceListener l)298     /*package*/ BluetoothHeadset(Context context, ServiceListener l) {
299         mContext = context;
300         mServiceListener = l;
301         mAdapter = BluetoothAdapter.getDefaultAdapter();
302 
303         IBluetoothManager mgr = mAdapter.getBluetoothManager();
304         if (mgr != null) {
305             try {
306                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
307             } catch (RemoteException e) {
308                 Log.e(TAG,"",e);
309             }
310         }
311 
312         doBind();
313     }
314 
doBind()315     boolean doBind() {
316         try {
317             return mAdapter.getBluetoothManager().bindBluetoothProfileService(
318                     BluetoothProfile.HEADSET, mConnection);
319         } catch (RemoteException e) {
320             Log.e(TAG, "Unable to bind HeadsetService", e);
321         }
322         return false;
323     }
324 
doUnbind()325     void doUnbind() {
326         synchronized (mConnection) {
327             if (mService != null) {
328                 try {
329                     mAdapter.getBluetoothManager().unbindBluetoothProfileService(
330                             BluetoothProfile.HEADSET, mConnection);
331                 } catch (RemoteException e) {
332                     Log.e(TAG,"Unable to unbind HeadsetService", e);
333                 }
334             }
335         }
336     }
337 
338     /**
339      * Close the connection to the backing service.
340      * Other public functions of BluetoothHeadset will return default error
341      * results once close() has been called. Multiple invocations of close()
342      * are ok.
343      */
close()344     /*package*/ void close() {
345         if (VDBG) log("close()");
346 
347         IBluetoothManager mgr = mAdapter.getBluetoothManager();
348         if (mgr != null) {
349             try {
350                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
351             } catch (Exception e) {
352                 Log.e(TAG,"",e);
353             }
354         }
355         mServiceListener = null;
356         doUnbind();
357     }
358 
359     /**
360      * Initiate connection to a profile of the remote bluetooth device.
361      *
362      * <p> Currently, the system supports only 1 connection to the
363      * headset/handsfree profile. The API will automatically disconnect connected
364      * devices before connecting.
365      *
366      * <p> This API returns false in scenarios like the profile on the
367      * device is already connected or Bluetooth is not turned on.
368      * When this API returns true, it is guaranteed that
369      * connection state intent for the profile will be broadcasted with
370      * the state. Users can get the connection state of the profile
371      * from this intent.
372      *
373      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
374      * permission.
375      *
376      * @param device Remote Bluetooth Device
377      * @return false on immediate error,
378      *               true otherwise
379      * @hide
380      */
connect(BluetoothDevice device)381     public boolean connect(BluetoothDevice device) {
382         if (DBG) log("connect(" + device + ")");
383         if (mService != null && isEnabled() &&
384             isValidDevice(device)) {
385             try {
386                 return mService.connect(device);
387             } catch (RemoteException e) {
388                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
389                 return false;
390             }
391         }
392         if (mService == null) Log.w(TAG, "Proxy not attached to service");
393         return false;
394     }
395 
396     /**
397      * Initiate disconnection from a profile
398      *
399      * <p> This API will return false in scenarios like the profile on the
400      * Bluetooth device is not in connected state etc. When this API returns,
401      * true, it is guaranteed that the connection state change
402      * intent will be broadcasted with the state. Users can get the
403      * disconnection state of the profile from this intent.
404      *
405      * <p> If the disconnection is initiated by a remote device, the state
406      * will transition from {@link #STATE_CONNECTED} to
407      * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
408      * host (local) device the state will transition from
409      * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
410      * state {@link #STATE_DISCONNECTED}. The transition to
411      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
412      * two scenarios.
413      *
414      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
415      * permission.
416      *
417      * @param device Remote Bluetooth Device
418      * @return false on immediate error,
419      *               true otherwise
420      * @hide
421      */
disconnect(BluetoothDevice device)422     public boolean disconnect(BluetoothDevice device) {
423         if (DBG) log("disconnect(" + device + ")");
424         if (mService != null && isEnabled() &&
425             isValidDevice(device)) {
426             try {
427                 return mService.disconnect(device);
428             } catch (RemoteException e) {
429               Log.e(TAG, Log.getStackTraceString(new Throwable()));
430               return false;
431             }
432         }
433         if (mService == null) Log.w(TAG, "Proxy not attached to service");
434         return false;
435     }
436 
437     /**
438      * {@inheritDoc}
439      */
getConnectedDevices()440     public List<BluetoothDevice> getConnectedDevices() {
441         if (VDBG) log("getConnectedDevices()");
442         if (mService != null && isEnabled()) {
443             try {
444                 return mService.getConnectedDevices();
445             } catch (RemoteException e) {
446                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
447                 return new ArrayList<BluetoothDevice>();
448             }
449         }
450         if (mService == null) Log.w(TAG, "Proxy not attached to service");
451         return new ArrayList<BluetoothDevice>();
452     }
453 
454     /**
455      * {@inheritDoc}
456      */
getDevicesMatchingConnectionStates(int[] states)457     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
458         if (VDBG) log("getDevicesMatchingStates()");
459         if (mService != null && isEnabled()) {
460             try {
461                 return mService.getDevicesMatchingConnectionStates(states);
462             } catch (RemoteException e) {
463                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
464                 return new ArrayList<BluetoothDevice>();
465             }
466         }
467         if (mService == null) Log.w(TAG, "Proxy not attached to service");
468         return new ArrayList<BluetoothDevice>();
469     }
470 
471     /**
472      * {@inheritDoc}
473      */
getConnectionState(BluetoothDevice device)474     public int getConnectionState(BluetoothDevice device) {
475         if (VDBG) log("getConnectionState(" + device + ")");
476         if (mService != null && isEnabled() &&
477             isValidDevice(device)) {
478             try {
479                 return mService.getConnectionState(device);
480             } catch (RemoteException e) {
481                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
482                 return BluetoothProfile.STATE_DISCONNECTED;
483             }
484         }
485         if (mService == null) Log.w(TAG, "Proxy not attached to service");
486         return BluetoothProfile.STATE_DISCONNECTED;
487     }
488 
489     /**
490      * Set priority of the profile
491      *
492      * <p> The device should already be paired.
493      *  Priority can be one of {@link #PRIORITY_ON} or
494      * {@link #PRIORITY_OFF},
495      *
496      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
497      * permission.
498      *
499      * @param device Paired bluetooth device
500      * @param priority
501      * @return true if priority is set, false on error
502      * @hide
503      */
setPriority(BluetoothDevice device, int priority)504     public boolean setPriority(BluetoothDevice device, int priority) {
505         if (DBG) log("setPriority(" + device + ", " + priority + ")");
506         if (mService != null && isEnabled() &&
507             isValidDevice(device)) {
508             if (priority != BluetoothProfile.PRIORITY_OFF &&
509                 priority != BluetoothProfile.PRIORITY_ON) {
510               return false;
511             }
512             try {
513                 return mService.setPriority(device, priority);
514             } catch (RemoteException e) {
515                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
516                 return false;
517             }
518         }
519         if (mService == null) Log.w(TAG, "Proxy not attached to service");
520         return false;
521     }
522 
523     /**
524      * Get the priority of the profile.
525      *
526      * <p> The priority can be any of:
527      * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
528      * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
529      *
530      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
531      *
532      * @param device Bluetooth device
533      * @return priority of the device
534      * @hide
535      */
getPriority(BluetoothDevice device)536     public int getPriority(BluetoothDevice device) {
537         if (VDBG) log("getPriority(" + device + ")");
538         if (mService != null && isEnabled() &&
539             isValidDevice(device)) {
540             try {
541                 return mService.getPriority(device);
542             } catch (RemoteException e) {
543                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
544                 return PRIORITY_OFF;
545             }
546         }
547         if (mService == null) Log.w(TAG, "Proxy not attached to service");
548         return PRIORITY_OFF;
549     }
550 
551     /**
552      * Start Bluetooth voice recognition. This methods sends the voice
553      * recognition AT command to the headset and establishes the
554      * audio connection.
555      *
556      * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
557      * If this function returns true, this intent will be broadcasted with
558      * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
559      *
560      * <p> {@link #EXTRA_STATE} will transition from
561      * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
562      * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
563      * in case of failure to establish the audio connection.
564      *
565      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
566      *
567      * @param device Bluetooth headset
568      * @return false if there is no headset connected of if the
569      *               connected headset doesn't support voice recognition
570      *               or on error, true otherwise
571      */
startVoiceRecognition(BluetoothDevice device)572     public boolean startVoiceRecognition(BluetoothDevice device) {
573         if (DBG) log("startVoiceRecognition()");
574         if (mService != null && isEnabled() &&
575             isValidDevice(device)) {
576             try {
577                 return mService.startVoiceRecognition(device);
578             } catch (RemoteException e) {
579                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
580             }
581         }
582         if (mService == null) Log.w(TAG, "Proxy not attached to service");
583         return false;
584     }
585 
586     /**
587      * Stop Bluetooth Voice Recognition mode, and shut down the
588      * Bluetooth audio path.
589      *
590      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
591      *
592      * @param device Bluetooth headset
593      * @return false if there is no headset connected
594      *               or on error, true otherwise
595      */
stopVoiceRecognition(BluetoothDevice device)596     public boolean stopVoiceRecognition(BluetoothDevice device) {
597         if (DBG) log("stopVoiceRecognition()");
598         if (mService != null && isEnabled() &&
599             isValidDevice(device)) {
600             try {
601                 return mService.stopVoiceRecognition(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 false;
608     }
609 
610     /**
611      * Check if Bluetooth SCO audio is connected.
612      *
613      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
614      *
615      * @param device Bluetooth headset
616      * @return true if SCO is connected,
617      *         false otherwise or on error
618      */
isAudioConnected(BluetoothDevice device)619     public boolean isAudioConnected(BluetoothDevice device) {
620         if (VDBG) log("isAudioConnected()");
621         if (mService != null && isEnabled() &&
622             isValidDevice(device)) {
623             try {
624               return mService.isAudioConnected(device);
625             } catch (RemoteException e) {
626               Log.e(TAG,  Log.getStackTraceString(new Throwable()));
627             }
628         }
629         if (mService == null) Log.w(TAG, "Proxy not attached to service");
630         return false;
631     }
632 
633     /**
634      * Get battery usage hint for Bluetooth Headset service.
635      * This is a monotonically increasing integer. Wraps to 0 at
636      * Integer.MAX_INT, and at boot.
637      * Current implementation returns the number of AT commands handled since
638      * boot. This is a good indicator for spammy headset/handsfree units that
639      * can keep the device awake by polling for cellular status updates. As a
640      * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
641      *
642      * @param device the bluetooth headset.
643      * @return monotonically increasing battery usage hint, or a negative error
644      *         code on error
645      * @hide
646      */
getBatteryUsageHint(BluetoothDevice device)647     public int getBatteryUsageHint(BluetoothDevice device) {
648         if (VDBG) log("getBatteryUsageHint()");
649         if (mService != null && isEnabled() &&
650             isValidDevice(device)) {
651             try {
652                 return mService.getBatteryUsageHint(device);
653             } catch (RemoteException e) {
654                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
655             }
656         }
657         if (mService == null) Log.w(TAG, "Proxy not attached to service");
658         return -1;
659     }
660 
661     /**
662      * Indicates if current platform supports voice dialing over bluetooth SCO.
663      *
664      * @return true if voice dialing over bluetooth is supported, false otherwise.
665      * @hide
666      */
isBluetoothVoiceDialingEnabled(Context context)667     public static boolean isBluetoothVoiceDialingEnabled(Context context) {
668         return context.getResources().getBoolean(
669                 com.android.internal.R.bool.config_bluetooth_sco_off_call);
670     }
671 
672     /**
673      * Accept the incoming connection.
674      * Note: This is an internal function and shouldn't be exposed
675      *
676      * @hide
677      */
acceptIncomingConnect(BluetoothDevice device)678     public boolean acceptIncomingConnect(BluetoothDevice device) {
679         if (DBG) log("acceptIncomingConnect");
680         if (mService != null && isEnabled()) {
681             try {
682                 return mService.acceptIncomingConnect(device);
683             } catch (RemoteException e) {Log.e(TAG, e.toString());}
684         } else {
685             Log.w(TAG, "Proxy not attached to service");
686             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
687         }
688         return false;
689     }
690 
691     /**
692      * Reject the incoming connection.
693      * @hide
694      */
rejectIncomingConnect(BluetoothDevice device)695     public boolean rejectIncomingConnect(BluetoothDevice device) {
696         if (DBG) log("rejectIncomingConnect");
697         if (mService != null) {
698             try {
699                 return mService.rejectIncomingConnect(device);
700             } catch (RemoteException e) {Log.e(TAG, e.toString());}
701         } else {
702             Log.w(TAG, "Proxy not attached to service");
703             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
704         }
705         return false;
706     }
707 
708     /**
709      * Get the current audio state of the Headset.
710      * Note: This is an internal function and shouldn't be exposed
711      *
712      * @hide
713      */
getAudioState(BluetoothDevice device)714     public int getAudioState(BluetoothDevice device) {
715         if (VDBG) log("getAudioState");
716         if (mService != null && !isDisabled()) {
717             try {
718                 return mService.getAudioState(device);
719             } catch (RemoteException e) {Log.e(TAG, e.toString());}
720         } else {
721             Log.w(TAG, "Proxy not attached to service");
722             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
723         }
724         return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
725     }
726 
727     /**
728      * Sets whether audio routing is allowed. When set to {@code false}, the AG will not route any
729      * audio to the HF unless explicitly told to.
730      * This method should be used in cases where the SCO channel is shared between multiple profiles
731      * and must be delegated by a source knowledgeable
732      * Note: This is an internal function and shouldn't be exposed
733      *
734      * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise.
735      *
736      * @hide
737      */
setAudioRouteAllowed(boolean allowed)738     public void setAudioRouteAllowed(boolean allowed) {
739         if (VDBG) log("setAudioRouteAllowed");
740         if (mService != null && isEnabled()) {
741             try {
742                 mService.setAudioRouteAllowed(allowed);
743             } catch (RemoteException e) {Log.e(TAG, e.toString());}
744         } else {
745             Log.w(TAG, "Proxy not attached to service");
746             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
747         }
748     }
749 
750     /**
751      * Returns whether audio routing is allowed. see {@link #setAudioRouteAllowed(boolean)}.
752      * Note: This is an internal function and shouldn't be exposed
753      *
754      * @hide
755      */
getAudioRouteAllowed()756     public boolean getAudioRouteAllowed() {
757         if (VDBG) log("getAudioRouteAllowed");
758         if (mService != null && isEnabled()) {
759             try {
760                 return mService.getAudioRouteAllowed();
761             } catch (RemoteException e) {Log.e(TAG, e.toString());}
762         } else {
763             Log.w(TAG, "Proxy not attached to service");
764             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
765         }
766         return false;
767     }
768 
769     /**
770      * Check if Bluetooth SCO audio is connected.
771      *
772      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
773      *
774      * @return true if SCO is connected,
775      *         false otherwise or on error
776      * @hide
777      */
isAudioOn()778     public boolean isAudioOn() {
779         if (VDBG) log("isAudioOn()");
780         if (mService != null && isEnabled()) {
781             try {
782               return mService.isAudioOn();
783             } catch (RemoteException e) {
784               Log.e(TAG,  Log.getStackTraceString(new Throwable()));
785             }
786         }
787         if (mService == null) Log.w(TAG, "Proxy not attached to service");
788         return false;
789 
790     }
791 
792     /**
793      * Initiates a connection of headset audio.
794      * It setup SCO channel with remote connected headset device.
795      *
796      * @return true if successful
797      *         false if there was some error such as
798      *               there is no connected headset
799      * @hide
800      */
connectAudio()801     public boolean connectAudio() {
802         if (mService != null && isEnabled()) {
803             try {
804                 return mService.connectAudio();
805             } catch (RemoteException e) {
806                 Log.e(TAG, e.toString());
807             }
808         } else {
809             Log.w(TAG, "Proxy not attached to service");
810             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
811         }
812         return false;
813     }
814 
815     /**
816      * Initiates a disconnection of headset audio.
817      * It tears down the SCO channel from remote headset device.
818      *
819      * @return true if successful
820      *         false if there was some error such as
821      *               there is no connected SCO channel
822      * @hide
823      */
disconnectAudio()824     public boolean disconnectAudio() {
825         if (mService != null && isEnabled()) {
826             try {
827                 return mService.disconnectAudio();
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         return false;
836     }
837 
838     /**
839      * Initiates a SCO channel connection with the headset (if connected).
840      * Also initiates a virtual voice call for Handsfree devices as many devices
841      * do not accept SCO audio without a call.
842      * This API allows the handsfree device to be used for routing non-cellular
843      * call audio.
844      *
845      * @param device Remote Bluetooth Device
846      * @return true if successful, false if there was some error.
847      * @hide
848      */
startScoUsingVirtualVoiceCall(BluetoothDevice device)849     public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
850         if (DBG) log("startScoUsingVirtualVoiceCall()");
851         if (mService != null && isEnabled() && isValidDevice(device)) {
852             try {
853                 return mService.startScoUsingVirtualVoiceCall(device);
854             } catch (RemoteException e) {
855                 Log.e(TAG, e.toString());
856             }
857         } else {
858             Log.w(TAG, "Proxy not attached to service");
859             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
860         }
861         return false;
862     }
863 
864     /**
865      * Terminates an ongoing SCO connection and the associated virtual
866      * call.
867      *
868      * @param device Remote Bluetooth Device
869      * @return true if successful, false if there was some error.
870      * @hide
871      */
stopScoUsingVirtualVoiceCall(BluetoothDevice device)872     public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
873         if (DBG) log("stopScoUsingVirtualVoiceCall()");
874         if (mService != null && isEnabled() && isValidDevice(device)) {
875             try {
876                 return mService.stopScoUsingVirtualVoiceCall(device);
877             } catch (RemoteException e) {
878                 Log.e(TAG, e.toString());
879             }
880         } else {
881             Log.w(TAG, "Proxy not attached to service");
882             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
883         }
884         return false;
885     }
886 
887     /**
888      * Notify Headset of phone state change.
889      * This is a backdoor for phone app to call BluetoothHeadset since
890      * there is currently not a good way to get precise call state change outside
891      * of phone app.
892      *
893      * @hide
894      */
phoneStateChanged(int numActive, int numHeld, int callState, String number, int type)895     public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
896                                   int type) {
897         if (mService != null && isEnabled()) {
898             try {
899                 mService.phoneStateChanged(numActive, numHeld, callState, number, type);
900             } catch (RemoteException e) {
901                 Log.e(TAG, e.toString());
902             }
903         } else {
904             Log.w(TAG, "Proxy not attached to service");
905             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
906         }
907     }
908 
909     /**
910      * Send Headset of CLCC response
911      *
912      * @hide
913      */
clccResponse(int index, int direction, int status, int mode, boolean mpty, String number, int type)914     public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
915                              String number, int type) {
916         if (mService != null && isEnabled()) {
917             try {
918                 mService.clccResponse(index, direction, status, mode, mpty, number, type);
919             } catch (RemoteException e) {
920                 Log.e(TAG, e.toString());
921             }
922         } else {
923             Log.w(TAG, "Proxy not attached to service");
924             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
925         }
926     }
927 
928     /**
929      * Sends a vendor-specific unsolicited result code to the headset.
930      *
931      * <p>The actual string to be sent is <code>command + ": " + arg</code>.
932      * For example, if {@code command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg}
933      * is {@code "0"}, the string <code>"+ANDROID: 0"</code> will be sent.
934      *
935      * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}.
936      *
937      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
938      *
939      * @param device Bluetooth headset.
940      * @param command A vendor-specific command.
941      * @param arg The argument that will be attached to the command.
942      * @return {@code false} if there is no headset connected, or if the command is not an allowed
943      *         vendor-specific unsolicited result code, or on error. {@code true} otherwise.
944      * @throws IllegalArgumentException if {@code command} is {@code null}.
945      */
sendVendorSpecificResultCode(BluetoothDevice device, String command, String arg)946     public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
947             String arg) {
948         if (DBG) {
949             log("sendVendorSpecificResultCode()");
950         }
951         if (command == null) {
952             throw new IllegalArgumentException("command is null");
953         }
954         if (mService != null && isEnabled() &&
955                 isValidDevice(device)) {
956             try {
957                 return mService.sendVendorSpecificResultCode(device, command, arg);
958             } catch (RemoteException e) {
959                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
960             }
961         }
962         if (mService == null) {
963             Log.w(TAG, "Proxy not attached to service");
964         }
965         return false;
966     }
967 
968     /**
969      * enable WBS codec setting.
970      *
971      * @return true if successful
972      *         false if there was some error such as
973      *               there is no connected headset
974      * @hide
975      */
enableWBS()976     public boolean enableWBS() {
977         if (mService != null && isEnabled()) {
978             try {
979                 return mService.enableWBS();
980             } catch (RemoteException e) {
981                 Log.e(TAG, e.toString());
982             }
983         } else {
984             Log.w(TAG, "Proxy not attached to service");
985             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
986         }
987         return false;
988     }
989 
990     /**
991      * disable WBS codec settting. It set NBS codec.
992      *
993      * @return true if successful
994      *         false if there was some error such as
995      *               there is no connected headset
996      * @hide
997      */
disableWBS()998     public boolean disableWBS() {
999         if (mService != null && isEnabled()) {
1000             try {
1001                 return mService.disableWBS();
1002             } catch (RemoteException e) {
1003                 Log.e(TAG, e.toString());
1004             }
1005         } else {
1006             Log.w(TAG, "Proxy not attached to service");
1007             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1008         }
1009         return false;
1010     }
1011 
1012     /**
1013      * Send Headset the BIND response from AG to report change in the status of the
1014      * HF indicators to the headset
1015      *
1016      * @param ind_id Assigned Number of the indicator (defined by SIG)
1017      * @param ind_status
1018      * possible values- false-Indicator is disabled, no value changes shall be sent for this indicator
1019      *                  true-Indicator is enabled, value changes may be sent for this indicator
1020      * @hide
1021      */
bindResponse(int ind_id, boolean ind_status)1022     public void bindResponse(int ind_id, boolean ind_status) {
1023         if (mService != null && isEnabled()) {
1024             try {
1025                 mService.bindResponse(ind_id, ind_status);
1026             } catch (RemoteException e) {
1027                 Log.e(TAG, e.toString());
1028             }
1029         } else {
1030             Log.w(TAG, "Proxy not attached to service");
1031             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
1032         }
1033     }
1034 
1035     private final IBluetoothProfileServiceConnection mConnection
1036             = new IBluetoothProfileServiceConnection.Stub()  {
1037         @Override
1038         public void onServiceConnected(ComponentName className, IBinder service) {
1039             if (DBG) Log.d(TAG, "Proxy object connected");
1040             mService = IBluetoothHeadset.Stub.asInterface(service);
1041             mHandler.sendMessage(mHandler.obtainMessage(
1042                     MESSAGE_HEADSET_SERVICE_CONNECTED));
1043         }
1044         @Override
1045         public void onServiceDisconnected(ComponentName className) {
1046             if (DBG) Log.d(TAG, "Proxy object disconnected");
1047             mService = null;
1048             mHandler.sendMessage(mHandler.obtainMessage(
1049                     MESSAGE_HEADSET_SERVICE_DISCONNECTED));
1050         }
1051     };
1052 
isEnabled()1053     private boolean isEnabled() {
1054        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
1055        return false;
1056     }
1057 
isDisabled()1058     private boolean isDisabled() {
1059        if (mAdapter.getState() == BluetoothAdapter.STATE_OFF) return true;
1060        return false;
1061     }
1062 
isValidDevice(BluetoothDevice device)1063     private boolean isValidDevice(BluetoothDevice device) {
1064        if (device == null) return false;
1065 
1066        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
1067        return false;
1068     }
1069 
log(String msg)1070     private static void log(String msg) {
1071         Log.d(TAG, msg);
1072     }
1073 
1074     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
1075         @Override
1076         public void handleMessage(Message msg) {
1077             switch (msg.what) {
1078                 case MESSAGE_HEADSET_SERVICE_CONNECTED: {
1079                     if (mServiceListener != null) {
1080                         mServiceListener.onServiceConnected(BluetoothProfile.HEADSET,
1081                                 BluetoothHeadset.this);
1082                     }
1083                     break;
1084                 }
1085                 case MESSAGE_HEADSET_SERVICE_DISCONNECTED: {
1086                     if (mServiceListener != null) {
1087                         mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
1088                     }
1089                     break;
1090                 }
1091             }
1092         }
1093     };
1094 }
1095