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