Home | History | Annotate | Download | only in bluetooth
      1 /*
      2  * Copyright (C) 2014 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.content.ComponentName;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.ServiceConnection;
     23 import android.os.Bundle;
     24 import android.os.IBinder;
     25 import android.os.RemoteException;
     26 import android.util.Log;
     27 
     28 import java.util.ArrayList;
     29 import java.util.List;
     30 
     31 /**
     32  * Public API to control Hands Free Profile (HFP role only).
     33  * <p>
     34  * This class defines methods that shall be used by application to manage profile
     35  * connection, calls states and calls actions.
     36  * <p>
     37  *
     38  * @hide
     39  * */
     40 public final class BluetoothHeadsetClient implements BluetoothProfile {
     41     private static final String TAG = "BluetoothHeadsetClient";
     42     private static final boolean DBG = true;
     43     private static final boolean VDBG = false;
     44 
     45     /**
     46      * Intent sent whenever connection to remote changes.
     47      *
     48      * <p>It includes two extras:
     49      * <code>BluetoothProfile.EXTRA_PREVIOUS_STATE</code>
     50      * and <code>BluetoothProfile.EXTRA_STATE</code>, which
     51      * are mandatory.
     52      * <p>There are also non mandatory feature extras:
     53      * {@link #EXTRA_AG_FEATURE_3WAY_CALLING},
     54      * {@link #EXTRA_AG_FEATURE_VOICE_RECOGNITION},
     55      * {@link #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT},
     56      * {@link #EXTRA_AG_FEATURE_REJECT_CALL},
     57      * {@link #EXTRA_AG_FEATURE_ECC},
     58      * {@link #EXTRA_AG_FEATURE_RESPONSE_AND_HOLD},
     59      * {@link #EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL},
     60      * {@link #EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL},
     61      * {@link #EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT},
     62      * {@link #EXTRA_AG_FEATURE_MERGE},
     63      * {@link #EXTRA_AG_FEATURE_MERGE_AND_DETACH},
     64      * sent as boolean values only when <code>EXTRA_STATE</code>
     65      * is set to <code>STATE_CONNECTED</code>.</p>
     66      *
     67      * <p>Note that features supported by AG are being sent as
     68      * booleans with value <code>true</code>,
     69      * and not supported ones are <strong>not</strong> being sent at all.</p>
     70      */
     71     public static final String ACTION_CONNECTION_STATE_CHANGED =
     72         "android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED";
     73 
     74     /**
     75      * Intent sent whenever audio state changes.
     76      *
     77      * <p>It includes two mandatory extras:
     78      * {@link BluetoothProfile.EXTRA_STATE},
     79      * {@link BluetoothProfile.EXTRA_PREVIOUS_STATE},
     80      * with possible values:
     81      * {@link #STATE_AUDIO_CONNECTING},
     82      * {@link #STATE_AUDIO_CONNECTED},
     83      * {@link #STATE_AUDIO_DISCONNECTED}</p>
     84      * <p>When <code>EXTRA_STATE</code> is set
     85      * to </code>STATE_AUDIO_CONNECTED</code>,
     86      * it also includes {@link #EXTRA_AUDIO_WBS}
     87      * indicating wide band speech support.</p>
     88      */
     89     public static final String ACTION_AUDIO_STATE_CHANGED =
     90         "android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED";
     91 
     92     /**
     93      * Intent sending updates of the Audio Gateway state.
     94      * Each extra is being sent only when value it
     95      * represents has been changed recently on AG.
     96      * <p>It can contain one or more of the following extras:
     97      * {@link #EXTRA_NETWORK_STATUS},
     98      * {@link #EXTRA_NETWORK_SIGNAL_STRENGTH},
     99      * {@link #EXTRA_NETWORK_ROAMING},
    100      * {@link #EXTRA_BATTERY_LEVEL},
    101      * {@link #EXTRA_OPERATOR_NAME},
    102      * {@link #EXTRA_VOICE_RECOGNITION},
    103      * {@link #EXTRA_IN_BAND_RING}</p>
    104      */
    105     public static final String ACTION_AG_EVENT =
    106             "android.bluetooth.headsetclient.profile.action.AG_EVENT";
    107 
    108     /**
    109      * Intent sent whenever state of a call changes.
    110      *
    111      * <p>It includes:
    112      * {@link #EXTRA_CALL},
    113      * with value of {@link BluetoothHeadsetClientCall} instance,
    114      * representing actual call state.</p>
    115      */
    116     public static final String ACTION_CALL_CHANGED =
    117             "android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED";
    118 
    119     /**
    120      * Intent that notifies about the result of the last issued action.
    121      * Please note that not every action results in explicit action result code being sent.
    122      * Instead other notifications about new Audio Gateway state might be sent,
    123      * like <code>ACTION_AG_EVENT</code> with <code>EXTRA_VOICE_RECOGNITION</code> value
    124      * when for example user started voice recognition from HF unit.
    125      */
    126     public static final String ACTION_RESULT =
    127             "android.bluetooth.headsetclient.profile.action.RESULT";
    128 
    129     /**
    130      * Intent that notifies about the number attached to the last voice tag
    131      * recorded on AG.
    132      *
    133      * <p>It contains:
    134      * {@link #EXTRA_NUMBER},
    135      * with a <code>String</code> value representing phone number.</p>
    136      */
    137     public static final String ACTION_LAST_VTAG =
    138             "android.bluetooth.headsetclient.profile.action.LAST_VTAG";
    139 
    140     public static final int STATE_AUDIO_DISCONNECTED = 0;
    141     public static final int STATE_AUDIO_CONNECTING = 1;
    142     public static final int STATE_AUDIO_CONNECTED = 2;
    143 
    144     /**
    145      * Extra with information if connected audio is WBS.
    146      * <p>Possible values: <code>true</code>,
    147      *                     <code>false</code>.</p>
    148      */
    149     public static final String EXTRA_AUDIO_WBS =
    150             "android.bluetooth.headsetclient.extra.AUDIO_WBS";
    151 
    152     /**
    153      * Extra for AG_EVENT indicates network status.
    154      * <p>Value: 0 - network unavailable,
    155      *           1 - network available </p>
    156      */
    157     public static final String EXTRA_NETWORK_STATUS =
    158             "android.bluetooth.headsetclient.extra.NETWORK_STATUS";
    159     /**
    160      * Extra for AG_EVENT intent indicates network signal strength.
    161      * <p>Value: <code>Integer</code> representing signal strength.</p>
    162      */
    163     public static final String EXTRA_NETWORK_SIGNAL_STRENGTH =
    164             "android.bluetooth.headsetclient.extra.NETWORK_SIGNAL_STRENGTH";
    165     /**
    166      * Extra for AG_EVENT intent indicates roaming state.
    167      * <p>Value: 0 - no roaming
    168      *           1 - active roaming</p>
    169      */
    170     public static final String EXTRA_NETWORK_ROAMING =
    171             "android.bluetooth.headsetclient.extra.NETWORK_ROAMING";
    172     /**
    173      * Extra for AG_EVENT intent indicates the battery level.
    174      * <p>Value: <code>Integer</code> representing signal strength.</p>
    175      */
    176     public static final String EXTRA_BATTERY_LEVEL =
    177             "android.bluetooth.headsetclient.extra.BATTERY_LEVEL";
    178     /**
    179      * Extra for AG_EVENT intent indicates operator name.
    180      * <p>Value: <code>String</code> representing operator name.</p>
    181      */
    182     public static final String EXTRA_OPERATOR_NAME =
    183             "android.bluetooth.headsetclient.extra.OPERATOR_NAME";
    184     /**
    185      * Extra for AG_EVENT intent indicates voice recognition state.
    186      * <p>Value:
    187      *          0 - voice recognition stopped,
    188      *          1 - voice recognition started.</p>
    189      */
    190     public static final String EXTRA_VOICE_RECOGNITION =
    191             "android.bluetooth.headsetclient.extra.VOICE_RECOGNITION";
    192     /**
    193      * Extra for AG_EVENT intent indicates in band ring state.
    194      * <p>Value:
    195      *          0 - in band ring tone not supported, or
    196      *          1 - in band ring tone supported.</p>
    197      */
    198     public static final String EXTRA_IN_BAND_RING =
    199             "android.bluetooth.headsetclient.extra.IN_BAND_RING";
    200 
    201     /**
    202      * Extra for AG_EVENT intent indicates subscriber info.
    203      * <p>Value: <code>String</code> containing subscriber information.</p>
    204      */
    205     public static final String EXTRA_SUBSCRIBER_INFO =
    206             "android.bluetooth.headsetclient.extra.SUBSCRIBER_INFO";
    207 
    208     /**
    209      *  Extra for AG_CALL_CHANGED intent indicates the
    210      *  {@link BluetoothHeadsetClientCall} object that has changed.
    211      */
    212     public static final String EXTRA_CALL =
    213             "android.bluetooth.headsetclient.extra.CALL";
    214 
    215     /**
    216      * Extra for ACTION_LAST_VTAG intent.
    217      * <p>Value: <code>String</code> representing phone number
    218      * corresponding to last voice tag recorded on AG</p>
    219      */
    220     public static final String EXTRA_NUMBER =
    221             "android.bluetooth.headsetclient.extra.NUMBER";
    222 
    223     /**
    224      * Extra for ACTION_RESULT intent that shows the result code of
    225      * last issued action.
    226      * <p>Possible results:
    227      * {@link #ACTION_RESULT_OK},
    228      * {@link #ACTION_RESULT_ERROR},
    229      * {@link #ACTION_RESULT_ERROR_NO_CARRIER},
    230      * {@link #ACTION_RESULT_ERROR_BUSY},
    231      * {@link #ACTION_RESULT_ERROR_NO_ANSWER},
    232      * {@link #ACTION_RESULT_ERROR_DELAYED},
    233      * {@link #ACTION_RESULT_ERROR_BLACKLISTED},
    234      * {@link #ACTION_RESULT_ERROR_CME}</p>
    235      */
    236     public static final String EXTRA_RESULT_CODE =
    237             "android.bluetooth.headsetclient.extra.RESULT_CODE";
    238 
    239     /**
    240      * Extra for ACTION_RESULT intent that shows the extended result code of
    241      * last issued action.
    242      * <p>Value: <code>Integer</code> - error code.</p>
    243      */
    244     public static final String EXTRA_CME_CODE =
    245             "android.bluetooth.headsetclient.extra.CME_CODE";
    246 
    247     /* Extras for AG_FEATURES, extras type is boolean */
    248     // TODO verify if all of those are actually useful
    249     /**
    250      * AG feature: three way calling.
    251      */
    252     public final static String EXTRA_AG_FEATURE_3WAY_CALLING =
    253             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_3WAY_CALLING";
    254     /**
    255      * AG feature: voice recognition.
    256      */
    257     public final static String EXTRA_AG_FEATURE_VOICE_RECOGNITION =
    258             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_VOICE_RECOGNITION";
    259     /**
    260      * AG feature: fetching phone number for voice tagging procedure.
    261      */
    262     public final static String EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT =
    263             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT";
    264     /**
    265      * AG feature: ability to reject incoming call.
    266      */
    267     public final static String EXTRA_AG_FEATURE_REJECT_CALL =
    268             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_REJECT_CALL";
    269     /**
    270      * AG feature: enhanced call handling (terminate specific call, private consultation).
    271      */
    272     public final static String EXTRA_AG_FEATURE_ECC =
    273             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ECC";
    274     /**
    275      * AG feature: response and hold.
    276      */
    277     public final static String EXTRA_AG_FEATURE_RESPONSE_AND_HOLD =
    278             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RESPONSE_AND_HOLD";
    279     /**
    280      * AG call handling feature: accept held or waiting call in three way calling scenarios.
    281      */
    282     public final static String EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL =
    283             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL";
    284     /**
    285      * AG call handling feature: release held or waiting call in three way calling scenarios.
    286      */
    287     public final static String EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL =
    288             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL";
    289     /**
    290      * AG call handling feature: release active call and accept held or waiting call in three way
    291      * calling scenarios.
    292      */
    293     public final static String EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT =
    294             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT";
    295     /**
    296      * AG call handling feature: merge two calls, held and active - multi party conference mode.
    297      */
    298     public final static String EXTRA_AG_FEATURE_MERGE =
    299             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE";
    300     /**
    301      * AG call handling feature: merge calls and disconnect from multi party
    302      * conversation leaving peers connected to each other.
    303      * Note that this feature needs to be supported by mobile network operator
    304      * as it requires connection and billing transfer.
    305      */
    306     public final static String EXTRA_AG_FEATURE_MERGE_AND_DETACH =
    307             "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE_AND_DETACH";
    308 
    309     /* Action result codes */
    310     public final static int ACTION_RESULT_OK = 0;
    311     public final static int ACTION_RESULT_ERROR = 1;
    312     public final static int ACTION_RESULT_ERROR_NO_CARRIER = 2;
    313     public final static int ACTION_RESULT_ERROR_BUSY = 3;
    314     public final static int ACTION_RESULT_ERROR_NO_ANSWER = 4;
    315     public final static int ACTION_RESULT_ERROR_DELAYED = 5;
    316     public final static int ACTION_RESULT_ERROR_BLACKLISTED = 6;
    317     public final static int ACTION_RESULT_ERROR_CME = 7;
    318 
    319     /* Detailed CME error codes */
    320     public final static int CME_PHONE_FAILURE                           = 0;
    321     public final static int CME_NO_CONNECTION_TO_PHONE                  = 1;
    322     public final static int CME_OPERATION_NOT_ALLOWED                   = 3;
    323     public final static int CME_OPERATION_NOT_SUPPORTED                 = 4;
    324     public final static int CME_PHSIM_PIN_REQUIRED                      = 5;
    325     public final static int CME_PHFSIM_PIN_REQUIRED                     = 6;
    326     public final static int CME_PHFSIM_PUK_REQUIRED                     = 7;
    327     public final static int CME_SIM_NOT_INSERTED                        = 10;
    328     public final static int CME_SIM_PIN_REQUIRED                        = 11;
    329     public final static int CME_SIM_PUK_REQUIRED                        = 12;
    330     public final static int CME_SIM_FAILURE                             = 13;
    331     public final static int CME_SIM_BUSY                                = 14;
    332     public final static int CME_SIM_WRONG                               = 15;
    333     public final static int CME_INCORRECT_PASSWORD                      = 16;
    334     public final static int CME_SIM_PIN2_REQUIRED                       = 17;
    335     public final static int CME_SIM_PUK2_REQUIRED                       = 18;
    336     public final static int CME_MEMORY_FULL                             = 20;
    337     public final static int CME_INVALID_INDEX                           = 21;
    338     public final static int CME_NOT_FOUND                               = 22;
    339     public final static int CME_MEMORY_FAILURE                          = 23;
    340     public final static int CME_TEXT_STRING_TOO_LONG                    = 24;
    341     public final static int CME_INVALID_CHARACTER_IN_TEXT_STRING        = 25;
    342     public final static int CME_DIAL_STRING_TOO_LONG                    = 26;
    343     public final static int CME_INVALID_CHARACTER_IN_DIAL_STRING        = 27;
    344     public final static int CME_NO_NETWORK_SERVICE                      = 30;
    345     public final static int CME_NETWORK_TIMEOUT                         = 31;
    346     public final static int CME_EMERGENCY_SERVICE_ONLY                  = 32;
    347     public final static int CME_NO_SIMULTANOUS_VOIP_CS_CALLS            = 33;
    348     public final static int CME_NOT_SUPPORTED_FOR_VOIP                  = 34;
    349     public final static int CME_SIP_RESPONSE_CODE                       = 35;
    350     public final static int CME_NETWORK_PERSONALIZATION_PIN_REQUIRED    = 40;
    351     public final static int CME_NETWORK_PERSONALIZATION_PUK_REQUIRED    = 41;
    352     public final static int CME_NETWORK_SUBSET_PERSONALIZATION_PIN_REQUIRED   = 42;
    353     public final static int CME_NETWORK_SUBSET_PERSONALIZATION_PUK_REQUIRED   = 43;
    354     public final static int CME_SERVICE_PROVIDER_PERSONALIZATION_PIN_REQUIRED = 44;
    355     public final static int CME_SERVICE_PROVIDER_PERSONALIZATION_PUK_REQUIRED = 45;
    356     public final static int CME_CORPORATE_PERSONALIZATION_PIN_REQUIRED  = 46;
    357     public final static int CME_CORPORATE_PERSONALIZATION_PUK_REQUIRED  = 47;
    358     public final static int CME_HIDDEN_KEY_REQUIRED                     = 48;
    359     public final static int CME_EAP_NOT_SUPPORTED                       = 49;
    360     public final static int CME_INCORRECT_PARAMETERS                    = 50;
    361 
    362     /* Action policy for other calls when accepting call */
    363     public static final int CALL_ACCEPT_NONE = 0;
    364     public static final int CALL_ACCEPT_HOLD = 1;
    365     public static final int CALL_ACCEPT_TERMINATE = 2;
    366 
    367     private Context mContext;
    368     private ServiceListener mServiceListener;
    369     private IBluetoothHeadsetClient mService;
    370     private BluetoothAdapter mAdapter;
    371 
    372     final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
    373             new IBluetoothStateChangeCallback.Stub() {
    374                 @Override
    375                 public void onBluetoothStateChange(boolean up) {
    376                     if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
    377                     if (!up) {
    378                         if (VDBG) Log.d(TAG,"Unbinding service...");
    379                         synchronized (mConnection) {
    380                             try {
    381                                 mService = null;
    382                                 mContext.unbindService(mConnection);
    383                             } catch (Exception re) {
    384                                 Log.e(TAG,"",re);
    385                             }
    386                         }
    387                     } else {
    388                         synchronized (mConnection) {
    389                             try {
    390                                 if (mService == null) {
    391                                     if (VDBG) Log.d(TAG,"Binding service...");
    392                                     Intent intent = new Intent(IBluetoothHeadsetClient.class.getName());
    393                                     doBind();
    394                                 }
    395                             } catch (Exception re) {
    396                                 Log.e(TAG,"",re);
    397                             }
    398                         }
    399                     }
    400                 }
    401         };
    402 
    403     /**
    404      * Create a BluetoothHeadsetClient proxy object.
    405      */
    406     /*package*/ BluetoothHeadsetClient(Context context, ServiceListener l) {
    407         mContext = context;
    408         mServiceListener = l;
    409         mAdapter = BluetoothAdapter.getDefaultAdapter();
    410 
    411         IBluetoothManager mgr = mAdapter.getBluetoothManager();
    412         if (mgr != null) {
    413             try {
    414                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
    415             } catch (RemoteException e) {
    416                 Log.e(TAG,"",e);
    417             }
    418         }
    419 
    420         doBind();
    421     }
    422 
    423     boolean doBind() {
    424         Intent intent = new Intent(IBluetoothHeadsetClient.class.getName());
    425         ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
    426         intent.setComponent(comp);
    427         if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
    428                  android.os.Process.myUserHandle())) {
    429             Log.e(TAG, "Could not bind to Bluetooth Headset Client Service with " + intent);
    430             return false;
    431         }
    432         return true;
    433     }
    434 
    435     /**
    436      * Close the connection to the backing service.
    437      * Other public functions of BluetoothHeadsetClient will return default error
    438      * results once close() has been called. Multiple invocations of close()
    439      * are ok.
    440      */
    441     /*package*/ void close() {
    442         if (VDBG) log("close()");
    443 
    444         IBluetoothManager mgr = mAdapter.getBluetoothManager();
    445         if (mgr != null) {
    446             try {
    447                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
    448             } catch (Exception e) {
    449                 Log.e(TAG,"",e);
    450             }
    451         }
    452 
    453         synchronized (mConnection) {
    454             if (mService != null) {
    455                 try {
    456                     mService = null;
    457                     mContext.unbindService(mConnection);
    458                 } catch (Exception re) {
    459                     Log.e(TAG,"",re);
    460                 }
    461             }
    462         }
    463         mServiceListener = null;
    464     }
    465 
    466     /**
    467      * Connects to remote device.
    468      *
    469      * Currently, the system supports only 1 connection. So, in case of the
    470      * second connection, this implementation will disconnect already connected
    471      * device automatically and will process the new one.
    472      *
    473      * @param device    a remote device we want connect to
    474      * @return <code>true</code> if command has been issued successfully;
    475      *          <code>false</code> otherwise;
    476      *          upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED}
    477      *          intent.
    478      */
    479     public boolean connect(BluetoothDevice device) {
    480         if (DBG) log("connect(" + device + ")");
    481         if (mService != null && isEnabled() &&
    482                 isValidDevice(device)) {
    483             try {
    484                 return mService.connect(device);
    485             } catch (RemoteException e) {
    486                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    487                 return false;
    488             }
    489         }
    490         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    491         return false;
    492     }
    493 
    494     /**
    495      * Disconnects remote device
    496      *
    497      * @param device    a remote device we want disconnect
    498      * @return          <code>true</code> if command has been issued successfully;
    499      *                  <code>false</code> otherwise;
    500      *                  upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED}
    501      *                  intent.
    502      */
    503     public boolean disconnect(BluetoothDevice device) {
    504         if (DBG) log("disconnect(" + device + ")");
    505         if (mService != null && isEnabled() &&
    506                 isValidDevice(device)) {
    507             try {
    508                 return mService.disconnect(device);
    509             } catch (RemoteException e) {
    510               Log.e(TAG, Log.getStackTraceString(new Throwable()));
    511               return false;
    512             }
    513         }
    514         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    515         return false;
    516     }
    517 
    518     /**
    519      * Return the list of connected remote devices
    520      *
    521      * @return list of connected devices; empty list if nothing is connected.
    522      */
    523     @Override
    524     public List<BluetoothDevice> getConnectedDevices() {
    525         if (VDBG) log("getConnectedDevices()");
    526         if (mService != null && isEnabled()) {
    527             try {
    528                 return mService.getConnectedDevices();
    529             } catch (RemoteException e) {
    530                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    531                 return new ArrayList<BluetoothDevice>();
    532             }
    533         }
    534         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    535         return new ArrayList<BluetoothDevice>();
    536     }
    537 
    538     /**
    539      * Returns list of remote devices in a particular state
    540      *
    541      * @param states    collection of states
    542      * @return          list of devices that state matches the states listed in
    543      *                  <code>states</code>; empty list if nothing matches the
    544      *                  <code>states</code>
    545      */
    546     @Override
    547     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
    548         if (VDBG) log("getDevicesMatchingStates()");
    549         if (mService != null && isEnabled()) {
    550             try {
    551                 return mService.getDevicesMatchingConnectionStates(states);
    552             } catch (RemoteException e) {
    553                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    554                 return new ArrayList<BluetoothDevice>();
    555             }
    556         }
    557         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    558         return new ArrayList<BluetoothDevice>();
    559     }
    560 
    561     /**
    562      * Returns state of the <code>device</code>
    563      *
    564      * @param device    a remote device
    565      * @return          the state of connection of the device
    566      */
    567     @Override
    568     public int getConnectionState(BluetoothDevice device) {
    569         if (VDBG) log("getConnectionState(" + device + ")");
    570         if (mService != null && isEnabled() &&
    571                 isValidDevice(device)) {
    572             try {
    573                 return mService.getConnectionState(device);
    574             } catch (RemoteException e) {
    575                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    576                 return BluetoothProfile.STATE_DISCONNECTED;
    577             }
    578         }
    579         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    580         return BluetoothProfile.STATE_DISCONNECTED;
    581     }
    582 
    583     /**
    584      * Set priority of the profile
    585      *
    586      * The device should already be paired.
    587      */
    588     public boolean setPriority(BluetoothDevice device, int priority) {
    589         if (DBG) log("setPriority(" + device + ", " + priority + ")");
    590         if (mService != null && isEnabled() &&
    591                 isValidDevice(device)) {
    592             if (priority != BluetoothProfile.PRIORITY_OFF &&
    593                     priority != BluetoothProfile.PRIORITY_ON) {
    594               return false;
    595             }
    596             try {
    597                 return mService.setPriority(device, priority);
    598             } catch (RemoteException e) {
    599                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    600                 return false;
    601             }
    602         }
    603         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    604         return false;
    605     }
    606 
    607     /**
    608      * Get the priority of the profile.
    609      */
    610     public int getPriority(BluetoothDevice device) {
    611         if (VDBG) log("getPriority(" + device + ")");
    612         if (mService != null && isEnabled() &&
    613                 isValidDevice(device)) {
    614             try {
    615                 return mService.getPriority(device);
    616             } catch (RemoteException e) {
    617                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    618                 return PRIORITY_OFF;
    619             }
    620         }
    621         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    622         return PRIORITY_OFF;
    623     }
    624 
    625     /**
    626      * Starts voice recognition.
    627      *
    628      * @param device    remote device
    629      * @return          <code>true</code> if command has been issued successfully;
    630      *                   <code>false</code> otherwise;
    631      *                   upon completion HFP sends {@link #ACTION_AG_EVENT}
    632      *                   intent.
    633      *
    634      * <p>Feature required for successful execution is being reported by:
    635      * {@link #EXTRA_AG_FEATURE_VOICE_RECOGNITION}.
    636      * This method invocation will fail silently when feature is not supported.</p>
    637      */
    638     public boolean startVoiceRecognition(BluetoothDevice device) {
    639         if (DBG) log("startVoiceRecognition()");
    640         if (mService != null && isEnabled() &&
    641                 isValidDevice(device)) {
    642             try {
    643                 return mService.startVoiceRecognition(device);
    644             } catch (RemoteException e) {
    645                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    646             }
    647         }
    648         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    649         return false;
    650     }
    651 
    652     /**
    653      * Stops voice recognition.
    654      *
    655      * @param device    remote device
    656      * @return          <code>true</code> if command has been issued successfully;
    657      *                   <code>false</code> otherwise;
    658      *                   upon completion HFP sends {@link #ACTION_AG_EVENT}
    659      *                   intent.
    660      *
    661      * <p>Feature required for successful execution is being reported by:
    662      * {@link #EXTRA_AG_FEATURE_VOICE_RECOGNITION}.
    663      * This method invocation will fail silently when feature is not supported.</p>
    664      */
    665     public boolean stopVoiceRecognition(BluetoothDevice device) {
    666         if (DBG) log("stopVoiceRecognition()");
    667         if (mService != null && isEnabled() &&
    668                 isValidDevice(device)) {
    669             try {
    670                 return mService.stopVoiceRecognition(device);
    671             } catch (RemoteException e) {
    672                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    673             }
    674         }
    675         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    676         return false;
    677     }
    678 
    679     /**
    680      * Returns list of all calls in any state.
    681      *
    682      * @param device    remote device
    683      * @return          list of calls; empty list if none call exists
    684      */
    685     public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) {
    686         if (DBG) log("getCurrentCalls()");
    687         if (mService != null && isEnabled() &&
    688                 isValidDevice(device)) {
    689             try {
    690                 return mService.getCurrentCalls(device);
    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 null;
    697     }
    698 
    699     /**
    700      * Returns list of current values of AG indicators.
    701      *
    702      * @param device    remote device
    703      * @return          bundle of AG  indicators; null if device is not in
    704      *                  CONNECTED state
    705      */
    706     public Bundle getCurrentAgEvents(BluetoothDevice device) {
    707         if (DBG) log("getCurrentCalls()");
    708         if (mService != null && isEnabled() &&
    709                 isValidDevice(device)) {
    710             try {
    711                 return mService.getCurrentAgEvents(device);
    712             } catch (RemoteException e) {
    713                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    714             }
    715         }
    716         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    717         return null;
    718     }
    719 
    720     /**
    721      * Accepts a call
    722      *
    723      * @param device    remote device
    724      * @param flag      action policy while accepting a call. Possible values
    725      *                   {@link #CALL_ACCEPT_NONE}, {@link #CALL_ACCEPT_HOLD},
    726      *                   {@link #CALL_ACCEPT_TERMINATE}
    727      * @return          <code>true</code> if command has been issued successfully;
    728      *                   <code>false</code> otherwise;
    729      *                   upon completion HFP sends {@link #ACTION_CALL_CHANGED}
    730      *                   intent.
    731      */
    732     public boolean acceptCall(BluetoothDevice device, int flag) {
    733         if (DBG) log("acceptCall()");
    734         if (mService != null && isEnabled() &&
    735                 isValidDevice(device)) {
    736             try {
    737                 return mService.acceptCall(device, flag);
    738             } catch (RemoteException e) {
    739                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    740             }
    741         }
    742         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    743         return false;
    744     }
    745 
    746     /**
    747      * Holds a call.
    748      *
    749      * @param device    remote device
    750      * @return          <code>true</code> if command has been issued successfully;
    751      *                   <code>false</code> otherwise;
    752      *                   upon completion HFP sends {@link #ACTION_CALL_CHANGED}
    753      *                   intent.
    754      */
    755     public boolean holdCall(BluetoothDevice device) {
    756         if (DBG) log("holdCall()");
    757         if (mService != null && isEnabled() &&
    758                 isValidDevice(device)) {
    759             try {
    760                 return mService.holdCall(device);
    761             } catch (RemoteException e) {
    762                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    763             }
    764         }
    765         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    766         return false;
    767     }
    768 
    769     /**
    770      * Rejects a call.
    771      *
    772      * @param device    remote device
    773      * @return          <code>true</code> if command has been issued successfully;
    774      *                   <code>false</code> otherwise;
    775      *                   upon completion HFP sends {@link #ACTION_CALL_CHANGED}
    776      *                   intent.
    777      *
    778      * <p>Feature required for successful execution is being reported by:
    779      * {@link #EXTRA_AG_FEATURE_REJECT_CALL}.
    780      * This method invocation will fail silently when feature is not supported.</p>
    781      */
    782     public boolean rejectCall(BluetoothDevice device) {
    783         if (DBG) log("rejectCall()");
    784         if (mService != null && isEnabled() &&
    785                 isValidDevice(device)) {
    786             try {
    787                 return mService.rejectCall(device);
    788             } catch (RemoteException e) {
    789                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    790             }
    791         }
    792         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    793         return false;
    794     }
    795 
    796     /**
    797      * Terminates a specified call.
    798      *
    799      * Works only when Extended Call Control is supported by Audio Gateway.
    800      *
    801      * @param device    remote device
    802      * @param index     index of the call to be terminated
    803      * @return          <code>true</code> if command has been issued successfully;
    804      *                   <code>false</code> otherwise;
    805      *                   upon completion HFP sends {@link #ACTION_CALL_CHANGED}
    806      *                   intent.
    807      *
    808      * <p>Feature required for successful execution is being reported by:
    809      * {@link #EXTRA_AG_FEATURE_ECC}.
    810      * This method invocation will fail silently when feature is not supported.</p>
    811      */
    812     public boolean terminateCall(BluetoothDevice device, int index) {
    813         if (DBG) log("terminateCall()");
    814         if (mService != null && isEnabled() &&
    815                 isValidDevice(device)) {
    816             try {
    817                 return mService.terminateCall(device, index);
    818             } catch (RemoteException e) {
    819                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    820             }
    821         }
    822         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    823         return false;
    824     }
    825 
    826     /**
    827      * Enters private mode with a specified call.
    828      *
    829      * Works only when Extended Call Control is supported by Audio Gateway.
    830      *
    831      * @param device    remote device
    832      * @param index     index of the call to connect in private mode
    833      * @return          <code>true</code> if command has been issued successfully;
    834      *                   <code>false</code> otherwise;
    835      *                   upon completion HFP sends {@link #ACTION_CALL_CHANGED}
    836      *                   intent.
    837      *
    838      * <p>Feature required for successful execution is being reported by:
    839      * {@link #EXTRA_AG_FEATURE_ECC}.
    840      * This method invocation will fail silently when feature is not supported.</p>
    841      */
    842     public boolean enterPrivateMode(BluetoothDevice device, int index) {
    843         if (DBG) log("enterPrivateMode()");
    844         if (mService != null && isEnabled() &&
    845                 isValidDevice(device)) {
    846             try {
    847                 return mService.enterPrivateMode(device, index);
    848             } catch (RemoteException e) {
    849                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    850             }
    851         }
    852         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    853         return false;
    854     }
    855 
    856     /**
    857      * Performs explicit call transfer.
    858      *
    859      * That means connect other calls and disconnect.
    860      *
    861      * @param device    remote device
    862      * @return          <code>true</code> if command has been issued successfully;
    863      *                   <code>false</code> otherwise;
    864      *                   upon completion HFP sends {@link #ACTION_CALL_CHANGED}
    865      *                   intent.
    866      *
    867      * <p>Feature required for successful execution is being reported by:
    868      * {@link #EXTRA_AG_FEATURE_MERGE_AND_DETACH}.
    869      * This method invocation will fail silently when feature is not supported.</p>
    870      */
    871     public boolean explicitCallTransfer(BluetoothDevice device) {
    872         if (DBG) log("explicitCallTransfer()");
    873         if (mService != null && isEnabled() &&
    874                 isValidDevice(device)) {
    875             try {
    876                 return mService.explicitCallTransfer(device);
    877             } catch (RemoteException e) {
    878                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    879             }
    880         }
    881         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    882         return false;
    883     }
    884 
    885     /**
    886      * Redials last number from Audio Gateway.
    887      *
    888      * @param device    remote device
    889      * @return          <code>true</code> if command has been issued successfully;
    890      *                   <code>false</code> otherwise;
    891      *                   upon completion HFP sends {@link #ACTION_CALL_CHANGED}
    892      *                   intent in case of success; {@link #ACTION_RESULT} is sent
    893      *                   otherwise;
    894      */
    895     public boolean redial(BluetoothDevice device) {
    896         if (DBG) log("redial()");
    897         if (mService != null && isEnabled() &&
    898                 isValidDevice(device)) {
    899             try {
    900                 return mService.redial(device);
    901             } catch (RemoteException e) {
    902                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    903             }
    904         }
    905         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    906         return false;
    907     }
    908 
    909     /**
    910      * Places a call with specified number.
    911      *
    912      * @param device    remote device
    913      * @param number    valid phone number
    914      * @return          <code>true</code> if command has been issued successfully;
    915      *                   <code>false</code> otherwise;
    916      *                   upon completion HFP sends {@link #ACTION_CALL_CHANGED}
    917      *                   intent in case of success; {@link #ACTION_RESULT} is sent
    918      *                   otherwise;
    919      */
    920     public boolean dial(BluetoothDevice device, String number) {
    921         if (DBG) log("dial()");
    922         if (mService != null && isEnabled() &&
    923                 isValidDevice(device)) {
    924             try {
    925                 return mService.dial(device, number);
    926             } catch (RemoteException e) {
    927                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    928             }
    929         }
    930         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    931         return false;
    932     }
    933 
    934     /**
    935      * Places a call to the number under specified memory location.
    936      *
    937      * @param device    remote device
    938      * @param location  valid memory location
    939      * @return          <code>true</code> if command has been issued successfully;
    940      *                   <code>false</code> otherwise;
    941      *                   upon completion HFP sends {@link #ACTION_CALL_CHANGED}
    942      *                   intent in case of success; {@link #ACTION_RESULT} is sent
    943      *                   otherwise;
    944      */
    945     public boolean dialMemory(BluetoothDevice device, int location) {
    946         if (DBG) log("dialMemory()");
    947         if (mService != null && isEnabled() &&
    948                 isValidDevice(device)) {
    949             try {
    950                 return mService.dialMemory(device, location);
    951             } catch (RemoteException e) {
    952                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    953             }
    954         }
    955         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    956         return false;
    957     }
    958 
    959     /**
    960      * Sends DTMF code.
    961      *
    962      * Possible code values : 0,1,2,3,4,5,6,7,8,9,A,B,C,D,*,#
    963      *
    964      * @param device    remote device
    965      * @param code  ASCII code
    966      * @return          <code>true</code> if command has been issued successfully;
    967      *                   <code>false</code> otherwise;
    968      *                   upon completion HFP sends {@link #ACTION_RESULT} intent;
    969      */
    970     public boolean sendDTMF(BluetoothDevice device, byte code) {
    971         if (DBG) log("sendDTMF()");
    972         if (mService != null && isEnabled() &&
    973                 isValidDevice(device)) {
    974             try {
    975                 return mService.sendDTMF(device, code);
    976             } catch (RemoteException e) {
    977                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
    978             }
    979         }
    980         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    981         return false;
    982     }
    983 
    984     /**
    985      * Get a number corresponding to last voice tag recorded on AG.
    986      *
    987      * @param device    remote device
    988      * @return          <code>true</code> if command has been issued successfully;
    989      *                   <code>false</code> otherwise;
    990      *                   upon completion HFP sends {@link #ACTION_LAST_VTAG}
    991      *                   or {@link #ACTION_RESULT} intent;
    992      *
    993      * <p>Feature required for successful execution is being reported by:
    994      * {@link #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT}.
    995      * This method invocation will fail silently when feature is not supported.</p>
    996      */
    997     public boolean getLastVoiceTagNumber(BluetoothDevice device) {
    998         if (DBG) log("getLastVoiceTagNumber()");
    999         if (mService != null && isEnabled() &&
   1000                 isValidDevice(device)) {
   1001             try {
   1002                 return mService.getLastVoiceTagNumber(device);
   1003             } catch (RemoteException e) {
   1004                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
   1005             }
   1006         }
   1007         if (mService == null) Log.w(TAG, "Proxy not attached to service");
   1008         return false;
   1009     }
   1010 
   1011     /**
   1012      * Accept the incoming connection.
   1013      */
   1014     public boolean acceptIncomingConnect(BluetoothDevice device) {
   1015         if (DBG) log("acceptIncomingConnect");
   1016         if (mService != null && isEnabled()) {
   1017             try {
   1018                 return mService.acceptIncomingConnect(device);
   1019             } catch (RemoteException e) {Log.e(TAG, e.toString());}
   1020         } else {
   1021             Log.w(TAG, "Proxy not attached to service");
   1022             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
   1023         }
   1024         return false;
   1025     }
   1026 
   1027     /**
   1028      * Reject the incoming connection.
   1029      */
   1030     public boolean rejectIncomingConnect(BluetoothDevice device) {
   1031         if (DBG) log("rejectIncomingConnect");
   1032         if (mService != null) {
   1033             try {
   1034                 return mService.rejectIncomingConnect(device);
   1035             } catch (RemoteException e) {Log.e(TAG, e.toString());}
   1036         } else {
   1037             Log.w(TAG, "Proxy not attached to service");
   1038             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
   1039         }
   1040         return false;
   1041     }
   1042 
   1043     /**
   1044      * Returns current audio state of Audio Gateway.
   1045      *
   1046      * Note: This is an internal function and shouldn't be exposed
   1047      */
   1048     public int getAudioState(BluetoothDevice device) {
   1049         if (VDBG) log("getAudioState");
   1050         if (mService != null && isEnabled()) {
   1051             try {
   1052                 return mService.getAudioState(device);
   1053             } catch (RemoteException e) {Log.e(TAG, e.toString());}
   1054         } else {
   1055             Log.w(TAG, "Proxy not attached to service");
   1056             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
   1057         }
   1058         return BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
   1059     }
   1060 
   1061     /**
   1062      * Initiates a connection of audio channel.
   1063      *
   1064      * It setup SCO channel with remote connected Handsfree AG device.
   1065      *
   1066      * @return          <code>true</code> if command has been issued successfully;
   1067      *                   <code>false</code> otherwise;
   1068      *                   upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED}
   1069      *                   intent;
   1070      */
   1071     public boolean connectAudio() {
   1072         if (mService != null && isEnabled()) {
   1073             try {
   1074                 return mService.connectAudio();
   1075             } catch (RemoteException e) {
   1076                 Log.e(TAG, e.toString());
   1077             }
   1078         } else {
   1079             Log.w(TAG, "Proxy not attached to service");
   1080             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
   1081         }
   1082         return false;
   1083     }
   1084 
   1085     /**
   1086      * Disconnects audio channel.
   1087      *
   1088      * It tears down the SCO channel from remote AG device.
   1089      *
   1090      * @return          <code>true</code> if command has been issued successfully;
   1091      *                   <code>false</code> otherwise;
   1092      *                   upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED}
   1093      *                   intent;
   1094      */
   1095     public boolean disconnectAudio() {
   1096         if (mService != null && isEnabled()) {
   1097             try {
   1098                 return mService.disconnectAudio();
   1099             } catch (RemoteException e) {
   1100                 Log.e(TAG, e.toString());
   1101             }
   1102         } else {
   1103             Log.w(TAG, "Proxy not attached to service");
   1104             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
   1105         }
   1106         return false;
   1107     }
   1108 
   1109     /**
   1110      * Get Audio Gateway features
   1111      *
   1112      * @param device    remote device
   1113      * @return          bundle of AG features; null if no service or
   1114      *                  AG not connected
   1115      */
   1116     public Bundle getCurrentAgFeatures(BluetoothDevice device) {
   1117         if (mService != null && isEnabled()) {
   1118             try {
   1119                 return mService.getCurrentAgFeatures(device);
   1120             } catch (RemoteException e) {
   1121                 Log.e(TAG, e.toString());
   1122             }
   1123         } else {
   1124             Log.w(TAG, "Proxy not attached to service");
   1125             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
   1126         }
   1127         return null;
   1128     }
   1129 
   1130 
   1131     private ServiceConnection mConnection = new ServiceConnection() {
   1132         @Override
   1133         public void onServiceConnected(ComponentName className, IBinder service) {
   1134             if (DBG) Log.d(TAG, "Proxy object connected");
   1135             mService = IBluetoothHeadsetClient.Stub.asInterface(service);
   1136 
   1137             if (mServiceListener != null) {
   1138                 mServiceListener.onServiceConnected(BluetoothProfile.HEADSET_CLIENT,
   1139                         BluetoothHeadsetClient.this);
   1140             }
   1141         }
   1142         @Override
   1143         public void onServiceDisconnected(ComponentName className) {
   1144             if (DBG) Log.d(TAG, "Proxy object disconnected");
   1145             mService = null;
   1146             if (mServiceListener != null) {
   1147                 mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET_CLIENT);
   1148             }
   1149         }
   1150     };
   1151 
   1152     private boolean isEnabled() {
   1153        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
   1154        return false;
   1155     }
   1156 
   1157     private boolean isValidDevice(BluetoothDevice device) {
   1158        if (device == null) return false;
   1159 
   1160        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
   1161        return false;
   1162     }
   1163 
   1164     private static void log(String msg) {
   1165         Log.d(TAG, msg);
   1166     }
   1167 }
   1168