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