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