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