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.content.Intent;
     24 import android.content.ServiceConnection;
     25 import android.os.IBinder;
     26 import android.os.RemoteException;
     27 import android.util.Log;
     28 
     29 import java.util.ArrayList;
     30 import java.util.List;
     31 
     32 /**
     33  * Public API for controlling the Bluetooth Headset Service. This includes both
     34  * Bluetooth Headset and Handsfree (v1.5) profiles.
     35  *
     36  * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
     37  * Service via IPC.
     38  *
     39  * <p> Use {@link BluetoothAdapter#getProfileProxy} to get
     40  * the BluetoothHeadset proxy object. Use
     41  * {@link BluetoothAdapter#closeProfileProxy} to close the service connection.
     42  *
     43  * <p> Android only supports one connected Bluetooth Headset at a time.
     44  * Each method is protected with its appropriate permission.
     45  */
     46 public final class BluetoothHeadset implements BluetoothProfile {
     47     private static final String TAG = "BluetoothHeadset";
     48     private static final boolean DBG = true;
     49     private static final boolean VDBG = false;
     50 
     51     /**
     52      * Intent used to broadcast the change in connection state of the Headset
     53      * profile.
     54      *
     55      * <p>This intent will have 3 extras:
     56      * <ul>
     57      *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
     58      *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
     59      *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
     60      * </ul>
     61      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
     62      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
     63      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
     64      *
     65      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
     66      * receive.
     67      */
     68     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     69     public static final String ACTION_CONNECTION_STATE_CHANGED =
     70         "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
     71 
     72     /**
     73      * Intent used to broadcast the change in the Audio Connection state of the
     74      * A2DP profile.
     75      *
     76      * <p>This intent will have 3 extras:
     77      * <ul>
     78      *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
     79      *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
     80      *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
     81      * </ul>
     82      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
     83      * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED},
     84      *
     85      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
     86      * to receive.
     87      */
     88     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     89     public static final String ACTION_AUDIO_STATE_CHANGED =
     90         "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
     91 
     92 
     93     /**
     94      * Intent used to broadcast that the headset has posted a
     95      * vendor-specific event.
     96      *
     97      * <p>This intent will have 4 extras and 1 category.
     98      * <ul>
     99      *  <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device
    100      *       </li>
    101      *  <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor
    102      *       specific command </li>
    103      *  <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT
    104      *       command type which can be one of  {@link #AT_CMD_TYPE_READ},
    105      *       {@link #AT_CMD_TYPE_TEST}, or {@link #AT_CMD_TYPE_SET},
    106      *       {@link #AT_CMD_TYPE_BASIC},{@link #AT_CMD_TYPE_ACTION}. </li>
    107      *  <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command
    108      *       arguments. </li>
    109      * </ul>
    110      *
    111      *<p> The category is the Company ID of the vendor defining the
    112      * vendor-specific command. {@link BluetoothAssignedNumbers}
    113      *
    114      * For example, for Plantronics specific events
    115      * Category will be {@link #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55
    116      *
    117      * <p> For example, an AT+XEVENT=foo,3 will get translated into
    118      * <ul>
    119      *   <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT </li>
    120      *   <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li>
    121      *   <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li>
    122      * </ul>
    123      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
    124      * to receive.
    125      */
    126     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    127     public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT =
    128             "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
    129 
    130     /**
    131      * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
    132      * intents that contains the name of the vendor-specific command.
    133      */
    134     public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD =
    135             "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD";
    136 
    137     /**
    138      * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
    139      * intents that contains the AT command type of the vendor-specific command.
    140      */
    141     public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE =
    142             "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE";
    143 
    144     /**
    145      * AT command type READ used with
    146      * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
    147      * For example, AT+VGM?. There are no arguments for this command type.
    148      */
    149     public static final int AT_CMD_TYPE_READ = 0;
    150 
    151     /**
    152      * AT command type TEST used with
    153      * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
    154      * For example, AT+VGM=?. There are no arguments for this command type.
    155      */
    156     public static final int AT_CMD_TYPE_TEST = 1;
    157 
    158     /**
    159      * AT command type SET used with
    160      * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
    161      * For example, AT+VGM=<args>.
    162      */
    163     public static final int AT_CMD_TYPE_SET = 2;
    164 
    165     /**
    166      * AT command type BASIC used with
    167      * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
    168      * For example, ATD. Single character commands and everything following the
    169      * character are arguments.
    170      */
    171     public static final int AT_CMD_TYPE_BASIC = 3;
    172 
    173     /**
    174      * AT command type ACTION used with
    175      * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
    176      * For example, AT+CHUP. There are no arguments for action commands.
    177      */
    178     public static final int AT_CMD_TYPE_ACTION = 4;
    179 
    180     /**
    181      * A Parcelable String array extra field in
    182      * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains
    183      * the arguments to the vendor-specific command.
    184      */
    185     public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS =
    186             "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS";
    187 
    188     /**
    189      * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
    190      * for the companyId
    191      */
    192     public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY  =
    193             "android.bluetooth.headset.intent.category.companyid";
    194 
    195     /**
    196      * A vendor-specific command for unsolicited result code.
    197      */
    198     public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID";
    199 
    200     /**
    201      * Headset state when SCO audio is not connected.
    202      * This state can be one of
    203      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
    204      * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
    205      */
    206     public static final int STATE_AUDIO_DISCONNECTED = 10;
    207 
    208     /**
    209      * Headset state when SCO audio is connecting.
    210      * This state can be one of
    211      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
    212      * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
    213      */
    214     public static final int STATE_AUDIO_CONNECTING = 11;
    215 
    216     /**
    217      * Headset state when SCO audio is connected.
    218      * This state can be one of
    219      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
    220      * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
    221      */
    222     public static final int STATE_AUDIO_CONNECTED = 12;
    223 
    224 
    225     private Context mContext;
    226     private ServiceListener mServiceListener;
    227     private IBluetoothHeadset mService;
    228     private BluetoothAdapter mAdapter;
    229 
    230     final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
    231             new IBluetoothStateChangeCallback.Stub() {
    232                 public void onBluetoothStateChange(boolean up) {
    233                     if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
    234                     if (!up) {
    235                         if (VDBG) Log.d(TAG,"Unbinding service...");
    236                         synchronized (mConnection) {
    237                             try {
    238                                 mService = null;
    239                                 mContext.unbindService(mConnection);
    240                             } catch (Exception re) {
    241                                 Log.e(TAG,"",re);
    242                             }
    243                         }
    244                     } else {
    245                         synchronized (mConnection) {
    246                             try {
    247                                 if (mService == null) {
    248                                     if (VDBG) Log.d(TAG,"Binding service...");
    249                                     doBind();
    250                                 }
    251                             } catch (Exception re) {
    252                                 Log.e(TAG,"",re);
    253                             }
    254                         }
    255                     }
    256                 }
    257         };
    258 
    259     /**
    260      * Create a BluetoothHeadset proxy object.
    261      */
    262     /*package*/ BluetoothHeadset(Context context, ServiceListener l) {
    263         mContext = context;
    264         mServiceListener = l;
    265         mAdapter = BluetoothAdapter.getDefaultAdapter();
    266 
    267         IBluetoothManager mgr = mAdapter.getBluetoothManager();
    268         if (mgr != null) {
    269             try {
    270                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
    271             } catch (RemoteException e) {
    272                 Log.e(TAG,"",e);
    273             }
    274         }
    275 
    276         doBind();
    277     }
    278 
    279     boolean doBind() {
    280         Intent intent = new Intent(IBluetoothHeadset.class.getName());
    281         ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
    282         intent.setComponent(comp);
    283         if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
    284                 android.os.Process.myUserHandle())) {
    285             Log.e(TAG, "Could not bind to Bluetooth Headset Service with " + intent);
    286             return false;
    287         }
    288         return true;
    289     }
    290 
    291     /**
    292      * Close the connection to the backing service.
    293      * Other public functions of BluetoothHeadset will return default error
    294      * results once close() has been called. Multiple invocations of close()
    295      * are ok.
    296      */
    297     /*package*/ void close() {
    298         if (VDBG) log("close()");
    299 
    300         IBluetoothManager mgr = mAdapter.getBluetoothManager();
    301         if (mgr != null) {
    302             try {
    303                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
    304             } catch (Exception e) {
    305                 Log.e(TAG,"",e);
    306             }
    307         }
    308 
    309         synchronized (mConnection) {
    310             if (mService != null) {
    311                 try {
    312                     mService = null;
    313                     mContext.unbindService(mConnection);
    314                 } catch (Exception re) {
    315                     Log.e(TAG,"",re);
    316                 }
    317             }
    318         }
    319         mServiceListener = null;
    320     }
    321 
    322     /**
    323      * Initiate connection to a profile of the remote bluetooth device.
    324      *
    325      * <p> Currently, the system supports only 1 connection to the
    326      * headset/handsfree profile. The API will automatically disconnect connected
    327      * devices before connecting.
    328      *
    329      * <p> This API returns false in scenarios like the profile on the
    330      * device is already connected or Bluetooth is not turned on.
    331      * When this API returns true, it is guaranteed that
    332      * connection state intent for the profile will be broadcasted with
    333      * the state. Users can get the connection state of the profile
    334      * from this intent.
    335      *
    336      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    337      * permission.
    338      *
    339      * @param device Remote Bluetooth Device
    340      * @return false on immediate error,
    341      *               true otherwise
    342      * @hide
    343      */
    344     public boolean connect(BluetoothDevice device) {
    345         if (DBG) log("connect(" + device + ")");
    346         if (mService != null && isEnabled() &&
    347             isValidDevice(device)) {
    348             try {
    349                 return mService.connect(device);
    350             } catch (RemoteException e) {
    351                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    352                 return false;
    353             }
    354         }
    355         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    356         return false;
    357     }
    358 
    359     /**
    360      * Initiate disconnection from a profile
    361      *
    362      * <p> This API will return false in scenarios like the profile on the
    363      * Bluetooth device is not in connected state etc. When this API returns,
    364      * true, it is guaranteed that the connection state change
    365      * intent will be broadcasted with the state. Users can get the
    366      * disconnection state of the profile from this intent.
    367      *
    368      * <p> If the disconnection is initiated by a remote device, the state
    369      * will transition from {@link #STATE_CONNECTED} to
    370      * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
    371      * host (local) device the state will transition from
    372      * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
    373      * state {@link #STATE_DISCONNECTED}. The transition to
    374      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
    375      * two scenarios.
    376      *
    377      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    378      * permission.
    379      *
    380      * @param device Remote Bluetooth Device
    381      * @return false on immediate error,
    382      *               true otherwise
    383      * @hide
    384      */
    385     public boolean disconnect(BluetoothDevice device) {
    386         if (DBG) log("disconnect(" + device + ")");
    387         if (mService != null && isEnabled() &&
    388             isValidDevice(device)) {
    389             try {
    390                 return mService.disconnect(device);
    391             } catch (RemoteException e) {
    392               Log.e(TAG, Log.getStackTraceString(new Throwable()));
    393               return false;
    394             }
    395         }
    396         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    397         return false;
    398     }
    399 
    400     /**
    401      * {@inheritDoc}
    402      */
    403     public List<BluetoothDevice> getConnectedDevices() {
    404         if (VDBG) log("getConnectedDevices()");
    405         if (mService != null && isEnabled()) {
    406             try {
    407                 return mService.getConnectedDevices();
    408             } catch (RemoteException e) {
    409                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    410                 return new ArrayList<BluetoothDevice>();
    411             }
    412         }
    413         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    414         return new ArrayList<BluetoothDevice>();
    415     }
    416 
    417     /**
    418      * {@inheritDoc}
    419      */
    420     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
    421         if (VDBG) log("getDevicesMatchingStates()");
    422         if (mService != null && isEnabled()) {
    423             try {
    424                 return mService.getDevicesMatchingConnectionStates(states);
    425             } catch (RemoteException e) {
    426                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    427                 return new ArrayList<BluetoothDevice>();
    428             }
    429         }
    430         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    431         return new ArrayList<BluetoothDevice>();
    432     }
    433 
    434     /**
    435      * {@inheritDoc}
    436      */
    437     public int getConnectionState(BluetoothDevice device) {
    438         if (VDBG) log("getConnectionState(" + device + ")");
    439         if (mService != null && isEnabled() &&
    440             isValidDevice(device)) {
    441             try {
    442                 return mService.getConnectionState(device);
    443             } catch (RemoteException e) {
    444                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    445                 return BluetoothProfile.STATE_DISCONNECTED;
    446             }
    447         }
    448         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    449         return BluetoothProfile.STATE_DISCONNECTED;
    450     }
    451 
    452     /**
    453      * Set priority of the profile
    454      *
    455      * <p> The device should already be paired.
    456      *  Priority can be one of {@link #PRIORITY_ON} or
    457      * {@link #PRIORITY_OFF},
    458      *
    459      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    460      * permission.
    461      *
    462      * @param device Paired bluetooth device
    463      * @param priority
    464      * @return true if priority is set, false on error
    465      * @hide
    466      */
    467     public boolean setPriority(BluetoothDevice device, int priority) {
    468         if (DBG) log("setPriority(" + device + ", " + priority + ")");
    469         if (mService != null && isEnabled() &&
    470             isValidDevice(device)) {
    471             if (priority != BluetoothProfile.PRIORITY_OFF &&
    472                 priority != BluetoothProfile.PRIORITY_ON) {
    473               return false;
    474             }
    475             try {
    476                 return mService.setPriority(device, priority);
    477             } catch (RemoteException e) {
    478                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    479                 return false;
    480             }
    481         }
    482         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    483         return false;
    484     }
    485 
    486     /**
    487      * Get the priority of the profile.
    488      *
    489      * <p> The priority can be any of:
    490      * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
    491      * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
    492      *
    493      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    494      *
    495      * @param device Bluetooth device
    496      * @return priority of the device
    497      * @hide
    498      */
    499     public int getPriority(BluetoothDevice device) {
    500         if (VDBG) log("getPriority(" + device + ")");
    501         if (mService != null && isEnabled() &&
    502             isValidDevice(device)) {
    503             try {
    504                 return mService.getPriority(device);
    505             } catch (RemoteException e) {
    506                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    507                 return PRIORITY_OFF;
    508             }
    509         }
    510         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    511         return PRIORITY_OFF;
    512     }
    513 
    514     /**
    515      * Start Bluetooth voice recognition. This methods sends the voice
    516      * recognition AT command to the headset and establishes the
    517      * audio connection.
    518      *
    519      * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
    520      * If this function returns true, this intent will be broadcasted with
    521      * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
    522      *
    523      * <p> {@link #EXTRA_STATE} will transition from
    524      * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
    525      * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
    526      * in case of failure to establish the audio connection.
    527      *
    528      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    529      *
    530      * @param device Bluetooth headset
    531      * @return false if there is no headset connected of if the
    532      *               connected headset doesn't support voice recognition
    533      *               or on error, true otherwise
    534      */
    535     public boolean startVoiceRecognition(BluetoothDevice device) {
    536         if (DBG) log("startVoiceRecognition()");
    537         if (mService != null && isEnabled() &&
    538             isValidDevice(device)) {
    539             try {
    540                 return mService.startVoiceRecognition(device);
    541             } catch (RemoteException e) {
    542                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    543             }
    544         }
    545         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    546         return false;
    547     }
    548 
    549     /**
    550      * Stop Bluetooth Voice Recognition mode, and shut down the
    551      * Bluetooth audio path.
    552      *
    553      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    554      *
    555      * @param device Bluetooth headset
    556      * @return false if there is no headset connected
    557      *               or on error, true otherwise
    558      */
    559     public boolean stopVoiceRecognition(BluetoothDevice device) {
    560         if (DBG) log("stopVoiceRecognition()");
    561         if (mService != null && isEnabled() &&
    562             isValidDevice(device)) {
    563             try {
    564                 return mService.stopVoiceRecognition(device);
    565             } catch (RemoteException e) {
    566                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    567             }
    568         }
    569         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    570         return false;
    571     }
    572 
    573     /**
    574      * Check if Bluetooth SCO audio is connected.
    575      *
    576      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    577      *
    578      * @param device Bluetooth headset
    579      * @return true if SCO is connected,
    580      *         false otherwise or on error
    581      */
    582     public boolean isAudioConnected(BluetoothDevice device) {
    583         if (VDBG) log("isAudioConnected()");
    584         if (mService != null && isEnabled() &&
    585             isValidDevice(device)) {
    586             try {
    587               return mService.isAudioConnected(device);
    588             } catch (RemoteException e) {
    589               Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    590             }
    591         }
    592         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    593         return false;
    594     }
    595 
    596     /**
    597      * Get battery usage hint for Bluetooth Headset service.
    598      * This is a monotonically increasing integer. Wraps to 0 at
    599      * Integer.MAX_INT, and at boot.
    600      * Current implementation returns the number of AT commands handled since
    601      * boot. This is a good indicator for spammy headset/handsfree units that
    602      * can keep the device awake by polling for cellular status updates. As a
    603      * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
    604      *
    605      * @param device the bluetooth headset.
    606      * @return monotonically increasing battery usage hint, or a negative error
    607      *         code on error
    608      * @hide
    609      */
    610     public int getBatteryUsageHint(BluetoothDevice device) {
    611         if (VDBG) log("getBatteryUsageHint()");
    612         if (mService != null && isEnabled() &&
    613             isValidDevice(device)) {
    614             try {
    615                 return mService.getBatteryUsageHint(device);
    616             } catch (RemoteException e) {
    617                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    618             }
    619         }
    620         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    621         return -1;
    622     }
    623 
    624     /**
    625      * Indicates if current platform supports voice dialing over bluetooth SCO.
    626      *
    627      * @return true if voice dialing over bluetooth is supported, false otherwise.
    628      * @hide
    629      */
    630     public static boolean isBluetoothVoiceDialingEnabled(Context context) {
    631         return context.getResources().getBoolean(
    632                 com.android.internal.R.bool.config_bluetooth_sco_off_call);
    633     }
    634 
    635     /**
    636      * Accept the incoming connection.
    637      * Note: This is an internal function and shouldn't be exposed
    638      *
    639      * @hide
    640      */
    641     public boolean acceptIncomingConnect(BluetoothDevice device) {
    642         if (DBG) log("acceptIncomingConnect");
    643         if (mService != null && isEnabled()) {
    644             try {
    645                 return mService.acceptIncomingConnect(device);
    646             } catch (RemoteException e) {Log.e(TAG, e.toString());}
    647         } else {
    648             Log.w(TAG, "Proxy not attached to service");
    649             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    650         }
    651         return false;
    652     }
    653 
    654     /**
    655      * Reject the incoming connection.
    656      * @hide
    657      */
    658     public boolean rejectIncomingConnect(BluetoothDevice device) {
    659         if (DBG) log("rejectIncomingConnect");
    660         if (mService != null) {
    661             try {
    662                 return mService.rejectIncomingConnect(device);
    663             } catch (RemoteException e) {Log.e(TAG, e.toString());}
    664         } else {
    665             Log.w(TAG, "Proxy not attached to service");
    666             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    667         }
    668         return false;
    669     }
    670 
    671     /**
    672      * Get the current audio state of the Headset.
    673      * Note: This is an internal function and shouldn't be exposed
    674      *
    675      * @hide
    676      */
    677     public int getAudioState(BluetoothDevice device) {
    678         if (VDBG) log("getAudioState");
    679         if (mService != null && !isDisabled()) {
    680             try {
    681                 return mService.getAudioState(device);
    682             } catch (RemoteException e) {Log.e(TAG, e.toString());}
    683         } else {
    684             Log.w(TAG, "Proxy not attached to service");
    685             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    686         }
    687         return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
    688     }
    689 
    690     /**
    691      * Check if Bluetooth SCO audio is connected.
    692      *
    693      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    694      *
    695      * @return true if SCO is connected,
    696      *         false otherwise or on error
    697      * @hide
    698      */
    699     public boolean isAudioOn() {
    700         if (VDBG) log("isAudioOn()");
    701         if (mService != null && isEnabled()) {
    702             try {
    703               return mService.isAudioOn();
    704             } catch (RemoteException e) {
    705               Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    706             }
    707         }
    708         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    709         return false;
    710 
    711     }
    712 
    713     /**
    714      * Initiates a connection of headset audio.
    715      * It setup SCO channel with remote connected headset device.
    716      *
    717      * @return true if successful
    718      *         false if there was some error such as
    719      *               there is no connected headset
    720      * @hide
    721      */
    722     public boolean connectAudio() {
    723         if (mService != null && isEnabled()) {
    724             try {
    725                 return mService.connectAudio();
    726             } catch (RemoteException e) {
    727                 Log.e(TAG, e.toString());
    728             }
    729         } else {
    730             Log.w(TAG, "Proxy not attached to service");
    731             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    732         }
    733         return false;
    734     }
    735 
    736     /**
    737      * Initiates a disconnection of headset audio.
    738      * It tears down the SCO channel from remote headset device.
    739      *
    740      * @return true if successful
    741      *         false if there was some error such as
    742      *               there is no connected SCO channel
    743      * @hide
    744      */
    745     public boolean disconnectAudio() {
    746         if (mService != null && isEnabled()) {
    747             try {
    748                 return mService.disconnectAudio();
    749             } catch (RemoteException e) {
    750                 Log.e(TAG, e.toString());
    751             }
    752         } else {
    753             Log.w(TAG, "Proxy not attached to service");
    754             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    755         }
    756         return false;
    757     }
    758 
    759     /**
    760      * Initiates a SCO channel connection with the headset (if connected).
    761      * Also initiates a virtual voice call for Handsfree devices as many devices
    762      * do not accept SCO audio without a call.
    763      * This API allows the handsfree device to be used for routing non-cellular
    764      * call audio.
    765      *
    766      * @param device Remote Bluetooth Device
    767      * @return true if successful, false if there was some error.
    768      * @hide
    769      */
    770     public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
    771         if (DBG) log("startScoUsingVirtualVoiceCall()");
    772         if (mService != null && isEnabled() && isValidDevice(device)) {
    773             try {
    774                 return mService.startScoUsingVirtualVoiceCall(device);
    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      * Terminates an ongoing SCO connection and the associated virtual
    787      * call.
    788      *
    789      * @param device Remote Bluetooth Device
    790      * @return true if successful, false if there was some error.
    791      * @hide
    792      */
    793     public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
    794         if (DBG) log("stopScoUsingVirtualVoiceCall()");
    795         if (mService != null && isEnabled() && isValidDevice(device)) {
    796             try {
    797                 return mService.stopScoUsingVirtualVoiceCall(device);
    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         return false;
    806     }
    807 
    808     /**
    809      * Notify Headset of phone state change.
    810      * This is a backdoor for phone app to call BluetoothHeadset since
    811      * there is currently not a good way to get precise call state change outside
    812      * of phone app.
    813      *
    814      * @hide
    815      */
    816     public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
    817                                   int type) {
    818         if (mService != null && isEnabled()) {
    819             try {
    820                 mService.phoneStateChanged(numActive, numHeld, callState, number, type);
    821             } catch (RemoteException e) {
    822                 Log.e(TAG, e.toString());
    823             }
    824         } else {
    825             Log.w(TAG, "Proxy not attached to service");
    826             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    827         }
    828     }
    829 
    830     /**
    831      * Send Headset of CLCC response
    832      *
    833      * @hide
    834      */
    835     public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
    836                              String number, int type) {
    837         if (mService != null && isEnabled()) {
    838             try {
    839                 mService.clccResponse(index, direction, status, mode, mpty, number, type);
    840             } catch (RemoteException e) {
    841                 Log.e(TAG, e.toString());
    842             }
    843         } else {
    844             Log.w(TAG, "Proxy not attached to service");
    845             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    846         }
    847     }
    848 
    849     /**
    850      * Sends a vendor-specific unsolicited result code to the headset.
    851      *
    852      * <p>The actual string to be sent is <code>command + ": " + arg</code>.
    853      * For example, if {@code command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg}
    854      * is {@code "0"}, the string <code>"+ANDROID: 0"</code> will be sent.
    855      *
    856      * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}.
    857      *
    858      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    859      *
    860      * @param device Bluetooth headset.
    861      * @param command A vendor-specific command.
    862      * @param arg The argument that will be attached to the command.
    863      * @return {@code false} if there is no headset connected, or if the command is not an allowed
    864      *         vendor-specific unsolicited result code, or on error. {@code true} otherwise.
    865      * @throws IllegalArgumentException if {@code command} is {@code null}.
    866      */
    867     public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
    868             String arg) {
    869         if (DBG) {
    870             log("sendVendorSpecificResultCode()");
    871         }
    872         if (command == null) {
    873             throw new IllegalArgumentException("command is null");
    874         }
    875         if (mService != null && isEnabled() &&
    876                 isValidDevice(device)) {
    877             try {
    878                 return mService.sendVendorSpecificResultCode(device, command, arg);
    879             } catch (RemoteException e) {
    880                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    881             }
    882         }
    883         if (mService == null) {
    884             Log.w(TAG, "Proxy not attached to service");
    885         }
    886         return false;
    887     }
    888 
    889     /**
    890      * enable WBS codec setting.
    891      *
    892      * @return true if successful
    893      *         false if there was some error such as
    894      *               there is no connected headset
    895      * @hide
    896      */
    897     public boolean enableWBS() {
    898         if (mService != null && isEnabled()) {
    899             try {
    900                 return mService.enableWBS();
    901             } catch (RemoteException e) {
    902                 Log.e(TAG, e.toString());
    903             }
    904         } else {
    905             Log.w(TAG, "Proxy not attached to service");
    906             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    907         }
    908         return false;
    909     }
    910 
    911     /**
    912      * disable WBS codec settting. It set NBS codec.
    913      *
    914      * @return true if successful
    915      *         false if there was some error such as
    916      *               there is no connected headset
    917      * @hide
    918      */
    919     public boolean disableWBS() {
    920         if (mService != null && isEnabled()) {
    921             try {
    922                 return mService.disableWBS();
    923             } catch (RemoteException e) {
    924                 Log.e(TAG, e.toString());
    925             }
    926         } else {
    927             Log.w(TAG, "Proxy not attached to service");
    928             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    929         }
    930         return false;
    931     }
    932 
    933     private final ServiceConnection mConnection = new ServiceConnection() {
    934         public void onServiceConnected(ComponentName className, IBinder service) {
    935             if (DBG) Log.d(TAG, "Proxy object connected");
    936             mService = IBluetoothHeadset.Stub.asInterface(service);
    937 
    938             if (mServiceListener != null) {
    939                 mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, BluetoothHeadset.this);
    940             }
    941         }
    942         public void onServiceDisconnected(ComponentName className) {
    943             if (DBG) Log.d(TAG, "Proxy object disconnected");
    944             mService = null;
    945             if (mServiceListener != null) {
    946                 mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
    947             }
    948         }
    949     };
    950 
    951     private boolean isEnabled() {
    952        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
    953        return false;
    954     }
    955 
    956     private boolean isDisabled() {
    957        if (mAdapter.getState() == BluetoothAdapter.STATE_OFF) return true;
    958        return false;
    959     }
    960 
    961     private boolean isValidDevice(BluetoothDevice device) {
    962        if (device == null) return false;
    963 
    964        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
    965        return false;
    966     }
    967 
    968     private static void log(String msg) {
    969         Log.d(TAG, msg);
    970     }
    971 }
    972