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.bindService(intent, mConnection, 0)) {
    284             Log.e(TAG, "Could not bind to Bluetooth Headset Service with " + intent);
    285             return false;
    286         }
    287         return true;
    288     }
    289 
    290     /**
    291      * Close the connection to the backing service.
    292      * Other public functions of BluetoothHeadset will return default error
    293      * results once close() has been called. Multiple invocations of close()
    294      * are ok.
    295      */
    296     /*package*/ void close() {
    297         if (VDBG) log("close()");
    298 
    299         IBluetoothManager mgr = mAdapter.getBluetoothManager();
    300         if (mgr != null) {
    301             try {
    302                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
    303             } catch (Exception e) {
    304                 Log.e(TAG,"",e);
    305             }
    306         }
    307 
    308         synchronized (mConnection) {
    309             if (mService != null) {
    310                 try {
    311                     mService = null;
    312                     mContext.unbindService(mConnection);
    313                 } catch (Exception re) {
    314                     Log.e(TAG,"",re);
    315                 }
    316             }
    317         }
    318         mServiceListener = null;
    319     }
    320 
    321     /**
    322      * Initiate connection to a profile of the remote bluetooth device.
    323      *
    324      * <p> Currently, the system supports only 1 connection to the
    325      * headset/handsfree profile. The API will automatically disconnect connected
    326      * devices before connecting.
    327      *
    328      * <p> This API returns false in scenarios like the profile on the
    329      * device is already connected or Bluetooth is not turned on.
    330      * When this API returns true, it is guaranteed that
    331      * connection state intent for the profile will be broadcasted with
    332      * the state. Users can get the connection state of the profile
    333      * from this intent.
    334      *
    335      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    336      * permission.
    337      *
    338      * @param device Remote Bluetooth Device
    339      * @return false on immediate error,
    340      *               true otherwise
    341      * @hide
    342      */
    343     public boolean connect(BluetoothDevice device) {
    344         if (DBG) log("connect(" + device + ")");
    345         if (mService != null && isEnabled() &&
    346             isValidDevice(device)) {
    347             try {
    348                 return mService.connect(device);
    349             } catch (RemoteException e) {
    350                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    351                 return false;
    352             }
    353         }
    354         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    355         return false;
    356     }
    357 
    358     /**
    359      * Initiate disconnection from a profile
    360      *
    361      * <p> This API will return false in scenarios like the profile on the
    362      * Bluetooth device is not in connected state etc. When this API returns,
    363      * true, it is guaranteed that the connection state change
    364      * intent will be broadcasted with the state. Users can get the
    365      * disconnection state of the profile from this intent.
    366      *
    367      * <p> If the disconnection is initiated by a remote device, the state
    368      * will transition from {@link #STATE_CONNECTED} to
    369      * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
    370      * host (local) device the state will transition from
    371      * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
    372      * state {@link #STATE_DISCONNECTED}. The transition to
    373      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
    374      * two scenarios.
    375      *
    376      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    377      * permission.
    378      *
    379      * @param device Remote Bluetooth Device
    380      * @return false on immediate error,
    381      *               true otherwise
    382      * @hide
    383      */
    384     public boolean disconnect(BluetoothDevice device) {
    385         if (DBG) log("disconnect(" + device + ")");
    386         if (mService != null && isEnabled() &&
    387             isValidDevice(device)) {
    388             try {
    389                 return mService.disconnect(device);
    390             } catch (RemoteException e) {
    391               Log.e(TAG, Log.getStackTraceString(new Throwable()));
    392               return false;
    393             }
    394         }
    395         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    396         return false;
    397     }
    398 
    399     /**
    400      * {@inheritDoc}
    401      */
    402     public List<BluetoothDevice> getConnectedDevices() {
    403         if (VDBG) log("getConnectedDevices()");
    404         if (mService != null && isEnabled()) {
    405             try {
    406                 return mService.getConnectedDevices();
    407             } catch (RemoteException e) {
    408                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    409                 return new ArrayList<BluetoothDevice>();
    410             }
    411         }
    412         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    413         return new ArrayList<BluetoothDevice>();
    414     }
    415 
    416     /**
    417      * {@inheritDoc}
    418      */
    419     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
    420         if (VDBG) log("getDevicesMatchingStates()");
    421         if (mService != null && isEnabled()) {
    422             try {
    423                 return mService.getDevicesMatchingConnectionStates(states);
    424             } catch (RemoteException e) {
    425                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    426                 return new ArrayList<BluetoothDevice>();
    427             }
    428         }
    429         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    430         return new ArrayList<BluetoothDevice>();
    431     }
    432 
    433     /**
    434      * {@inheritDoc}
    435      */
    436     public int getConnectionState(BluetoothDevice device) {
    437         if (VDBG) log("getConnectionState(" + device + ")");
    438         if (mService != null && isEnabled() &&
    439             isValidDevice(device)) {
    440             try {
    441                 return mService.getConnectionState(device);
    442             } catch (RemoteException e) {
    443                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    444                 return BluetoothProfile.STATE_DISCONNECTED;
    445             }
    446         }
    447         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    448         return BluetoothProfile.STATE_DISCONNECTED;
    449     }
    450 
    451     /**
    452      * Set priority of the profile
    453      *
    454      * <p> The device should already be paired.
    455      *  Priority can be one of {@link #PRIORITY_ON} or
    456      * {@link #PRIORITY_OFF},
    457      *
    458      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    459      * permission.
    460      *
    461      * @param device Paired bluetooth device
    462      * @param priority
    463      * @return true if priority is set, false on error
    464      * @hide
    465      */
    466     public boolean setPriority(BluetoothDevice device, int priority) {
    467         if (DBG) log("setPriority(" + device + ", " + priority + ")");
    468         if (mService != null && isEnabled() &&
    469             isValidDevice(device)) {
    470             if (priority != BluetoothProfile.PRIORITY_OFF &&
    471                 priority != BluetoothProfile.PRIORITY_ON) {
    472               return false;
    473             }
    474             try {
    475                 return mService.setPriority(device, priority);
    476             } catch (RemoteException e) {
    477                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    478                 return false;
    479             }
    480         }
    481         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    482         return false;
    483     }
    484 
    485     /**
    486      * Get the priority of the profile.
    487      *
    488      * <p> The priority can be any of:
    489      * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
    490      * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
    491      *
    492      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    493      *
    494      * @param device Bluetooth device
    495      * @return priority of the device
    496      * @hide
    497      */
    498     public int getPriority(BluetoothDevice device) {
    499         if (VDBG) log("getPriority(" + device + ")");
    500         if (mService != null && isEnabled() &&
    501             isValidDevice(device)) {
    502             try {
    503                 return mService.getPriority(device);
    504             } catch (RemoteException e) {
    505                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    506                 return PRIORITY_OFF;
    507             }
    508         }
    509         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    510         return PRIORITY_OFF;
    511     }
    512 
    513     /**
    514      * Start Bluetooth voice recognition. This methods sends the voice
    515      * recognition AT command to the headset and establishes the
    516      * audio connection.
    517      *
    518      * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
    519      * If this function returns true, this intent will be broadcasted with
    520      * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
    521      *
    522      * <p> {@link #EXTRA_STATE} will transition from
    523      * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
    524      * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
    525      * in case of failure to establish the audio connection.
    526      *
    527      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    528      *
    529      * @param device Bluetooth headset
    530      * @return false if there is no headset connected of if the
    531      *               connected headset doesn't support voice recognition
    532      *               or on error, true otherwise
    533      */
    534     public boolean startVoiceRecognition(BluetoothDevice device) {
    535         if (DBG) log("startVoiceRecognition()");
    536         if (mService != null && isEnabled() &&
    537             isValidDevice(device)) {
    538             try {
    539                 return mService.startVoiceRecognition(device);
    540             } catch (RemoteException e) {
    541                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    542             }
    543         }
    544         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    545         return false;
    546     }
    547 
    548     /**
    549      * Stop Bluetooth Voice Recognition mode, and shut down the
    550      * Bluetooth audio path.
    551      *
    552      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    553      *
    554      * @param device Bluetooth headset
    555      * @return false if there is no headset connected
    556      *               or on error, true otherwise
    557      */
    558     public boolean stopVoiceRecognition(BluetoothDevice device) {
    559         if (DBG) log("stopVoiceRecognition()");
    560         if (mService != null && isEnabled() &&
    561             isValidDevice(device)) {
    562             try {
    563                 return mService.stopVoiceRecognition(device);
    564             } catch (RemoteException e) {
    565                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    566             }
    567         }
    568         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    569         return false;
    570     }
    571 
    572     /**
    573      * Check if Bluetooth SCO audio is connected.
    574      *
    575      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    576      *
    577      * @param device Bluetooth headset
    578      * @return true if SCO is connected,
    579      *         false otherwise or on error
    580      */
    581     public boolean isAudioConnected(BluetoothDevice device) {
    582         if (VDBG) log("isAudioConnected()");
    583         if (mService != null && isEnabled() &&
    584             isValidDevice(device)) {
    585             try {
    586               return mService.isAudioConnected(device);
    587             } catch (RemoteException e) {
    588               Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    589             }
    590         }
    591         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    592         return false;
    593     }
    594 
    595     /**
    596      * Get battery usage hint for Bluetooth Headset service.
    597      * This is a monotonically increasing integer. Wraps to 0 at
    598      * Integer.MAX_INT, and at boot.
    599      * Current implementation returns the number of AT commands handled since
    600      * boot. This is a good indicator for spammy headset/handsfree units that
    601      * can keep the device awake by polling for cellular status updates. As a
    602      * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
    603      *
    604      * @param device the bluetooth headset.
    605      * @return monotonically increasing battery usage hint, or a negative error
    606      *         code on error
    607      * @hide
    608      */
    609     public int getBatteryUsageHint(BluetoothDevice device) {
    610         if (VDBG) log("getBatteryUsageHint()");
    611         if (mService != null && isEnabled() &&
    612             isValidDevice(device)) {
    613             try {
    614                 return mService.getBatteryUsageHint(device);
    615             } catch (RemoteException e) {
    616                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    617             }
    618         }
    619         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    620         return -1;
    621     }
    622 
    623     /**
    624      * Indicates if current platform supports voice dialing over bluetooth SCO.
    625      *
    626      * @return true if voice dialing over bluetooth is supported, false otherwise.
    627      * @hide
    628      */
    629     public static boolean isBluetoothVoiceDialingEnabled(Context context) {
    630         return context.getResources().getBoolean(
    631                 com.android.internal.R.bool.config_bluetooth_sco_off_call);
    632     }
    633 
    634     /**
    635      * Accept the incoming connection.
    636      * Note: This is an internal function and shouldn't be exposed
    637      *
    638      * @hide
    639      */
    640     public boolean acceptIncomingConnect(BluetoothDevice device) {
    641         if (DBG) log("acceptIncomingConnect");
    642         if (mService != null && isEnabled()) {
    643             try {
    644                 return mService.acceptIncomingConnect(device);
    645             } catch (RemoteException e) {Log.e(TAG, e.toString());}
    646         } else {
    647             Log.w(TAG, "Proxy not attached to service");
    648             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    649         }
    650         return false;
    651     }
    652 
    653     /**
    654      * Reject the incoming connection.
    655      * @hide
    656      */
    657     public boolean rejectIncomingConnect(BluetoothDevice device) {
    658         if (DBG) log("rejectIncomingConnect");
    659         if (mService != null) {
    660             try {
    661                 return mService.rejectIncomingConnect(device);
    662             } catch (RemoteException e) {Log.e(TAG, e.toString());}
    663         } else {
    664             Log.w(TAG, "Proxy not attached to service");
    665             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    666         }
    667         return false;
    668     }
    669 
    670     /**
    671      * Get the current audio state of the Headset.
    672      * Note: This is an internal function and shouldn't be exposed
    673      *
    674      * @hide
    675      */
    676     public int getAudioState(BluetoothDevice device) {
    677         if (VDBG) log("getAudioState");
    678         if (mService != null && !isDisabled()) {
    679             try {
    680                 return mService.getAudioState(device);
    681             } catch (RemoteException e) {Log.e(TAG, e.toString());}
    682         } else {
    683             Log.w(TAG, "Proxy not attached to service");
    684             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    685         }
    686         return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
    687     }
    688 
    689     /**
    690      * Check if Bluetooth SCO audio is connected.
    691      *
    692      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    693      *
    694      * @return true if SCO is connected,
    695      *         false otherwise or on error
    696      * @hide
    697      */
    698     public boolean isAudioOn() {
    699         if (VDBG) log("isAudioOn()");
    700         if (mService != null && isEnabled()) {
    701             try {
    702               return mService.isAudioOn();
    703             } catch (RemoteException e) {
    704               Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    705             }
    706         }
    707         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    708         return false;
    709 
    710     }
    711 
    712     /**
    713      * Initiates a connection of headset audio.
    714      * It setup SCO channel with remote connected headset device.
    715      *
    716      * @return true if successful
    717      *         false if there was some error such as
    718      *               there is no connected headset
    719      * @hide
    720      */
    721     public boolean connectAudio() {
    722         if (mService != null && isEnabled()) {
    723             try {
    724                 return mService.connectAudio();
    725             } catch (RemoteException e) {
    726                 Log.e(TAG, e.toString());
    727             }
    728         } else {
    729             Log.w(TAG, "Proxy not attached to service");
    730             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    731         }
    732         return false;
    733     }
    734 
    735     /**
    736      * Initiates a disconnection of headset audio.
    737      * It tears down the SCO channel from remote headset device.
    738      *
    739      * @return true if successful
    740      *         false if there was some error such as
    741      *               there is no connected SCO channel
    742      * @hide
    743      */
    744     public boolean disconnectAudio() {
    745         if (mService != null && isEnabled()) {
    746             try {
    747                 return mService.disconnectAudio();
    748             } catch (RemoteException e) {
    749                 Log.e(TAG, e.toString());
    750             }
    751         } else {
    752             Log.w(TAG, "Proxy not attached to service");
    753             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    754         }
    755         return false;
    756     }
    757 
    758     /**
    759      * Initiates a SCO channel connection with the headset (if connected).
    760      * Also initiates a virtual voice call for Handsfree devices as many devices
    761      * do not accept SCO audio without a call.
    762      * This API allows the handsfree device to be used for routing non-cellular
    763      * call audio.
    764      *
    765      * @param device Remote Bluetooth Device
    766      * @return true if successful, false if there was some error.
    767      * @hide
    768      */
    769     public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
    770         if (DBG) log("startScoUsingVirtualVoiceCall()");
    771         if (mService != null && isEnabled() && isValidDevice(device)) {
    772             try {
    773                 return mService.startScoUsingVirtualVoiceCall(device);
    774             } catch (RemoteException e) {
    775                 Log.e(TAG, e.toString());
    776             }
    777         } else {
    778             Log.w(TAG, "Proxy not attached to service");
    779             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    780         }
    781         return false;
    782     }
    783 
    784     /**
    785      * Terminates an ongoing SCO connection and the associated virtual
    786      * call.
    787      *
    788      * @param device Remote Bluetooth Device
    789      * @return true if successful, false if there was some error.
    790      * @hide
    791      */
    792     public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
    793         if (DBG) log("stopScoUsingVirtualVoiceCall()");
    794         if (mService != null && isEnabled() && isValidDevice(device)) {
    795             try {
    796                 return mService.stopScoUsingVirtualVoiceCall(device);
    797             } catch (RemoteException e) {
    798                 Log.e(TAG, e.toString());
    799             }
    800         } else {
    801             Log.w(TAG, "Proxy not attached to service");
    802             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    803         }
    804         return false;
    805     }
    806 
    807     /**
    808      * Notify Headset of phone state change.
    809      * This is a backdoor for phone app to call BluetoothHeadset since
    810      * there is currently not a good way to get precise call state change outside
    811      * of phone app.
    812      *
    813      * @hide
    814      */
    815     public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
    816                                   int type) {
    817         if (mService != null && isEnabled()) {
    818             try {
    819                 mService.phoneStateChanged(numActive, numHeld, callState, number, type);
    820             } catch (RemoteException e) {
    821                 Log.e(TAG, e.toString());
    822             }
    823         } else {
    824             Log.w(TAG, "Proxy not attached to service");
    825             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    826         }
    827     }
    828 
    829     /**
    830      * Send Headset of CLCC response
    831      *
    832      * @hide
    833      */
    834     public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
    835                              String number, int type) {
    836         if (mService != null && isEnabled()) {
    837             try {
    838                 mService.clccResponse(index, direction, status, mode, mpty, number, type);
    839             } catch (RemoteException e) {
    840                 Log.e(TAG, e.toString());
    841             }
    842         } else {
    843             Log.w(TAG, "Proxy not attached to service");
    844             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    845         }
    846     }
    847 
    848     /**
    849      * Sends a vendor-specific unsolicited result code to the headset.
    850      *
    851      * <p>The actual string to be sent is <code>command + ": " + arg</code>.
    852      * For example, if {@code command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg}
    853      * is {@code "0"}, the string <code>"+ANDROID: 0"</code> will be sent.
    854      *
    855      * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}.
    856      *
    857      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    858      *
    859      * @param device Bluetooth headset.
    860      * @param command A vendor-specific command.
    861      * @param arg The argument that will be attached to the command.
    862      * @return {@code false} if there is no headset connected, or if the command is not an allowed
    863      *         vendor-specific unsolicited result code, or on error. {@code true} otherwise.
    864      * @throws IllegalArgumentException if {@code command} is {@code null}.
    865      */
    866     public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
    867             String arg) {
    868         if (DBG) {
    869             log("sendVendorSpecificResultCode()");
    870         }
    871         if (command == null) {
    872             throw new IllegalArgumentException("command is null");
    873         }
    874         if (mService != null && isEnabled() &&
    875                 isValidDevice(device)) {
    876             try {
    877                 return mService.sendVendorSpecificResultCode(device, command, arg);
    878             } catch (RemoteException e) {
    879                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    880             }
    881         }
    882         if (mService == null) {
    883             Log.w(TAG, "Proxy not attached to service");
    884         }
    885         return false;
    886     }
    887 
    888     private final ServiceConnection mConnection = new ServiceConnection() {
    889         public void onServiceConnected(ComponentName className, IBinder service) {
    890             if (DBG) Log.d(TAG, "Proxy object connected");
    891             mService = IBluetoothHeadset.Stub.asInterface(service);
    892 
    893             if (mServiceListener != null) {
    894                 mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, BluetoothHeadset.this);
    895             }
    896         }
    897         public void onServiceDisconnected(ComponentName className) {
    898             if (DBG) Log.d(TAG, "Proxy object disconnected");
    899             mService = null;
    900             if (mServiceListener != null) {
    901                 mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
    902             }
    903         }
    904     };
    905 
    906     private boolean isEnabled() {
    907        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
    908        return false;
    909     }
    910 
    911     private boolean isDisabled() {
    912        if (mAdapter.getState() == BluetoothAdapter.STATE_OFF) return true;
    913        return false;
    914     }
    915 
    916     private boolean isValidDevice(BluetoothDevice device) {
    917        if (device == null) return false;
    918 
    919        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
    920        return false;
    921     }
    922 
    923     private static void log(String msg) {
    924         Log.d(TAG, msg);
    925     }
    926 }
    927