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