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