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