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