Home | History | Annotate | Download | only in hfp
      1 /*
      2  * Copyright (C) 2012 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 /**
     18  * Bluetooth Handset StateMachine
     19  *                      (Disconnected)
     20  *                           |    ^
     21  *                   CONNECT |    | DISCONNECTED
     22  *                           V    |
     23  *                         (Pending)
     24  *                           |    ^
     25  *                 CONNECTED |    | CONNECT
     26  *                           V    |
     27  *                        (Connected)
     28  *                           |    ^
     29  *             CONNECT_AUDIO |    | DISCONNECT_AUDIO
     30  *                           V    |
     31  *                         (AudioOn)
     32  */
     33 package com.android.bluetooth.hfp;
     34 
     35 import android.bluetooth.BluetoothAdapter;
     36 import android.bluetooth.BluetoothAssignedNumbers;
     37 import android.bluetooth.BluetoothDevice;
     38 import android.bluetooth.BluetoothHeadset;
     39 import android.bluetooth.BluetoothProfile;
     40 import android.bluetooth.BluetoothUuid;
     41 import android.bluetooth.IBluetooth;
     42 import android.bluetooth.IBluetoothHeadsetPhone;
     43 import android.content.ComponentName;
     44 import android.content.Context;
     45 import android.content.Intent;
     46 import android.content.ServiceConnection;
     47 import android.content.ActivityNotFoundException;
     48 import android.media.AudioManager;
     49 import android.net.Uri;
     50 import android.os.IBinder;
     51 import android.os.Message;
     52 import android.os.ParcelUuid;
     53 import android.os.RemoteException;
     54 import android.os.ServiceManager;
     55 import android.os.PowerManager;
     56 import android.os.PowerManager.WakeLock;
     57 import android.telephony.PhoneNumberUtils;
     58 import android.util.Log;
     59 import com.android.bluetooth.Utils;
     60 import com.android.bluetooth.btservice.AdapterService;
     61 import com.android.internal.util.IState;
     62 import com.android.internal.util.State;
     63 import com.android.internal.util.StateMachine;
     64 import java.util.ArrayList;
     65 import java.util.List;
     66 import java.util.Set;
     67 
     68 final class HeadsetStateMachine extends StateMachine {
     69     private static final String TAG = "HeadsetStateMachine";
     70     private static final boolean DBG = false;
     71     //For Debugging only
     72     private static int sRefCount=0;
     73 
     74     private static final String HEADSET_NAME = "bt_headset_name";
     75     private static final String HEADSET_NREC = "bt_headset_nrec";
     76 
     77     static final int CONNECT = 1;
     78     static final int DISCONNECT = 2;
     79     static final int CONNECT_AUDIO = 3;
     80     static final int DISCONNECT_AUDIO = 4;
     81     static final int VOICE_RECOGNITION_START = 5;
     82     static final int VOICE_RECOGNITION_STOP = 6;
     83 
     84     // message.obj is an intent AudioManager.VOLUME_CHANGED_ACTION
     85     // EXTRA_VOLUME_STREAM_TYPE is STREAM_BLUETOOTH_SCO
     86     static final int INTENT_SCO_VOLUME_CHANGED = 7;
     87     static final int SET_MIC_VOLUME = 8;
     88     static final int CALL_STATE_CHANGED = 9;
     89     static final int INTENT_BATTERY_CHANGED = 10;
     90     static final int DEVICE_STATE_CHANGED = 11;
     91     static final int ROAM_CHANGED = 12;
     92     static final int SEND_CCLC_RESPONSE = 13;
     93 
     94     static final int VIRTUAL_CALL_START = 14;
     95     static final int VIRTUAL_CALL_STOP = 15;
     96 
     97     private static final int STACK_EVENT = 101;
     98     private static final int DIALING_OUT_TIMEOUT = 102;
     99     private static final int START_VR_TIMEOUT = 103;
    100 
    101     private static final int CONNECT_TIMEOUT = 201;
    102 
    103     private static final int DIALING_OUT_TIMEOUT_VALUE = 10000;
    104     private static final int START_VR_TIMEOUT_VALUE = 5000;
    105 
    106     private static final ParcelUuid[] HEADSET_UUIDS = {
    107         BluetoothUuid.HSP,
    108         BluetoothUuid.Handsfree,
    109     };
    110 
    111     private Disconnected mDisconnected;
    112     private Pending mPending;
    113     private Connected mConnected;
    114     private AudioOn mAudioOn;
    115 
    116     private HeadsetService mService;
    117     private PowerManager mPowerManager;
    118     private boolean mVirtualCallStarted = false;
    119     private boolean mVoiceRecognitionStarted = false;
    120     private boolean mWaitingForVoiceRecognition = false;
    121     private WakeLock mStartVoiceRecognitionWakeLock;  // held while waiting for voice recognition
    122 
    123     private boolean mDialingOut = false;
    124     private AudioManager mAudioManager;
    125     private AtPhonebook mPhonebook;
    126 
    127     private static Intent sVoiceCommandIntent;
    128 
    129     private HeadsetPhoneState mPhoneState;
    130     private int mAudioState;
    131     private BluetoothAdapter mAdapter;
    132     private IBluetoothHeadsetPhone mPhoneProxy;
    133     private boolean mNativeAvailable;
    134 
    135     // mCurrentDevice is the device connected before the state changes
    136     // mTargetDevice is the device to be connected
    137     // mIncomingDevice is the device connecting to us, valid only in Pending state
    138     //                when mIncomingDevice is not null, both mCurrentDevice
    139     //                  and mTargetDevice are null
    140     //                when either mCurrentDevice or mTargetDevice is not null,
    141     //                  mIncomingDevice is null
    142     // Stable states
    143     //   No connection, Disconnected state
    144     //                  both mCurrentDevice and mTargetDevice are null
    145     //   Connected, Connected state
    146     //              mCurrentDevice is not null, mTargetDevice is null
    147     // Interim states
    148     //   Connecting to a device, Pending
    149     //                           mCurrentDevice is null, mTargetDevice is not null
    150     //   Disconnecting device, Connecting to new device
    151     //     Pending
    152     //     Both mCurrentDevice and mTargetDevice are not null
    153     //   Disconnecting device Pending
    154     //                        mCurrentDevice is not null, mTargetDevice is null
    155     //   Incoming connections Pending
    156     //                        Both mCurrentDevice and mTargetDevice are null
    157     private BluetoothDevice mCurrentDevice = null;
    158     private BluetoothDevice mTargetDevice = null;
    159     private BluetoothDevice mIncomingDevice = null;
    160 
    161     static {
    162         classInitNative();
    163     }
    164 
    165     private HeadsetStateMachine(HeadsetService context) {
    166         super(TAG);
    167         mService = context;
    168         mVoiceRecognitionStarted = false;
    169         mWaitingForVoiceRecognition = false;
    170 
    171         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
    172         mStartVoiceRecognitionWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
    173                                                        TAG + ":VoiceRecognition");
    174         mStartVoiceRecognitionWakeLock.setReferenceCounted(false);
    175 
    176         mDialingOut = false;
    177         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    178         mPhonebook = new AtPhonebook(mService, this);
    179         mPhoneState = new HeadsetPhoneState(context, this);
    180         mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
    181         mAdapter = BluetoothAdapter.getDefaultAdapter();
    182         if (!context.bindService(new Intent(IBluetoothHeadsetPhone.class.getName()),
    183                                  mConnection, 0)) {
    184             Log.e(TAG, "Could not bind to Bluetooth Headset Phone Service");
    185         }
    186 
    187         initializeNative();
    188         mNativeAvailable=true;
    189 
    190         mDisconnected = new Disconnected();
    191         mPending = new Pending();
    192         mConnected = new Connected();
    193         mAudioOn = new AudioOn();
    194 
    195         if (sVoiceCommandIntent == null) {
    196             sVoiceCommandIntent = new Intent(Intent.ACTION_VOICE_COMMAND);
    197             sVoiceCommandIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    198         }
    199 
    200         addState(mDisconnected);
    201         addState(mPending);
    202         addState(mConnected);
    203         addState(mAudioOn);
    204 
    205         setInitialState(mDisconnected);
    206     }
    207 
    208     static HeadsetStateMachine make(HeadsetService context) {
    209         Log.d(TAG, "make");
    210         HeadsetStateMachine hssm = new HeadsetStateMachine(context);
    211         hssm.start();
    212         return hssm;
    213     }
    214 
    215 
    216     public void doQuit() {
    217         quitNow();
    218     }
    219 
    220     public void cleanup() {
    221         if (mPhoneProxy != null) {
    222             if (DBG) Log.d(TAG,"Unbinding service...");
    223             synchronized (mConnection) {
    224                 try {
    225                     mPhoneProxy = null;
    226                     mService.unbindService(mConnection);
    227                 } catch (Exception re) {
    228                     Log.e(TAG,"Error unbinding from IBluetoothHeadsetPhone",re);
    229                 }
    230             }
    231         }
    232         if (mPhoneState != null) {
    233             mPhoneState.listenForPhoneState(false);
    234             mPhoneState.cleanup();
    235         }
    236         if (mPhonebook != null) {
    237             mPhonebook.cleanup();
    238         }
    239         if (mNativeAvailable) {
    240             cleanupNative();
    241             mNativeAvailable = false;
    242         }
    243     }
    244 
    245     private class Disconnected extends State {
    246         @Override
    247         public void enter() {
    248             log("Enter Disconnected: " + getCurrentMessage().what);
    249             mPhonebook.resetAtState();
    250             mPhoneState.listenForPhoneState(false);
    251         }
    252 
    253         @Override
    254         public boolean processMessage(Message message) {
    255             log("Disconnected process message: " + message.what);
    256             if (mCurrentDevice != null || mTargetDevice != null || mIncomingDevice != null) {
    257                 Log.e(TAG, "ERROR: current, target, or mIncomingDevice not null in Disconnected");
    258                 return NOT_HANDLED;
    259             }
    260 
    261             boolean retValue = HANDLED;
    262             switch(message.what) {
    263                 case CONNECT:
    264                     BluetoothDevice device = (BluetoothDevice) message.obj;
    265                     broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
    266                                    BluetoothProfile.STATE_DISCONNECTED);
    267 
    268                     if (!connectHfpNative(getByteAddress(device)) ) {
    269                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
    270                                        BluetoothProfile.STATE_CONNECTING);
    271                         break;
    272                     }
    273 
    274                     synchronized (HeadsetStateMachine.this) {
    275                         mTargetDevice = device;
    276                         transitionTo(mPending);
    277                     }
    278                     // TODO(BT) remove CONNECT_TIMEOUT when the stack
    279                     //          sends back events consistently
    280                     sendMessageDelayed(CONNECT_TIMEOUT, 30000);
    281                     break;
    282                 case DISCONNECT:
    283                     // ignore
    284                     break;
    285                 case INTENT_BATTERY_CHANGED:
    286                     processIntentBatteryChanged((Intent) message.obj);
    287                     break;
    288                 case ROAM_CHANGED:
    289                     processRoamChanged((Boolean) message.obj);
    290                     break;
    291                 case CALL_STATE_CHANGED:
    292                     processCallState((HeadsetCallState) message.obj,
    293                         ((message.arg1 == 1)?true:false));
    294                     break;
    295                 case STACK_EVENT:
    296                     StackEvent event = (StackEvent) message.obj;
    297                     if (DBG) {
    298                         log("event type: " + event.type);
    299                     }
    300                     switch (event.type) {
    301                         case EVENT_TYPE_CONNECTION_STATE_CHANGED:
    302                             processConnectionEvent(event.valueInt, event.device);
    303                             break;
    304                         default:
    305                             Log.e(TAG, "Unexpected stack event: " + event.type);
    306                             break;
    307                     }
    308                     break;
    309                 default:
    310                     return NOT_HANDLED;
    311             }
    312             return retValue;
    313         }
    314 
    315         @Override
    316         public void exit() {
    317             log("Exit Disconnected: " + getCurrentMessage().what);
    318         }
    319 
    320         // in Disconnected state
    321         private void processConnectionEvent(int state, BluetoothDevice device) {
    322             switch (state) {
    323             case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
    324                 Log.w(TAG, "Ignore HF DISCONNECTED event, device: " + device);
    325                 break;
    326             case HeadsetHalConstants.CONNECTION_STATE_CONNECTING:
    327                 if (okToConnect(device)){
    328                     Log.i(TAG,"Incoming Hf accepted");
    329                     broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
    330                                              BluetoothProfile.STATE_DISCONNECTED);
    331                     synchronized (HeadsetStateMachine.this) {
    332                         mIncomingDevice = device;
    333                         transitionTo(mPending);
    334                     }
    335                 } else {
    336                     Log.i(TAG,"Incoming Hf rejected. priority=" + mService.getPriority(device)+
    337                               " bondState=" + device.getBondState());
    338                     //reject the connection and stay in Disconnected state itself
    339                     disconnectHfpNative(getByteAddress(device));
    340                     // the other profile connection should be initiated
    341                     AdapterService adapterService = AdapterService.getAdapterService();
    342                     if ( adapterService != null) {
    343                         adapterService.connectOtherProfile(device,
    344                                                            AdapterService.PROFILE_CONN_REJECTED);
    345                     }
    346                 }
    347                 break;
    348             case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
    349                 Log.w(TAG, "HFP Connected from Disconnected state");
    350                 if (okToConnect(device)){
    351                     Log.i(TAG,"Incoming Hf accepted");
    352                     broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
    353                                              BluetoothProfile.STATE_DISCONNECTED);
    354                     synchronized (HeadsetStateMachine.this) {
    355                         mCurrentDevice = device;
    356                         transitionTo(mConnected);
    357                     }
    358                     configAudioParameters();
    359                 } else {
    360                     //reject the connection and stay in Disconnected state itself
    361                     Log.i(TAG,"Incoming Hf rejected. priority=" + mService.getPriority(device) +
    362                               " bondState=" + device.getBondState());
    363                     disconnectHfpNative(getByteAddress(device));
    364                     // the other profile connection should be initiated
    365                     AdapterService adapterService = AdapterService.getAdapterService();
    366                     if ( adapterService != null) {
    367                         adapterService.connectOtherProfile(device,
    368                                                            AdapterService.PROFILE_CONN_REJECTED);
    369                     }
    370                 }
    371                 break;
    372             case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING:
    373                 Log.w(TAG, "Ignore HF DISCONNECTING event, device: " + device);
    374                 break;
    375             default:
    376                 Log.e(TAG, "Incorrect state: " + state);
    377                 break;
    378             }
    379         }
    380     }
    381 
    382     private class Pending extends State {
    383         @Override
    384         public void enter() {
    385             log("Enter Pending: " + getCurrentMessage().what);
    386         }
    387 
    388         @Override
    389         public boolean processMessage(Message message) {
    390             log("Pending process message: " + message.what);
    391 
    392             boolean retValue = HANDLED;
    393             switch(message.what) {
    394                 case CONNECT:
    395                 case CONNECT_AUDIO:
    396                     deferMessage(message);
    397                     break;
    398                 case CONNECT_TIMEOUT:
    399                     onConnectionStateChanged(HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED,
    400                                              getByteAddress(mTargetDevice));
    401                     break;
    402                 case DISCONNECT:
    403                     BluetoothDevice device = (BluetoothDevice) message.obj;
    404                     if (mCurrentDevice != null && mTargetDevice != null &&
    405                         mTargetDevice.equals(device) ) {
    406                         // cancel connection to the mTargetDevice
    407                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
    408                                        BluetoothProfile.STATE_CONNECTING);
    409                         synchronized (HeadsetStateMachine.this) {
    410                             mTargetDevice = null;
    411                         }
    412                     } else {
    413                         deferMessage(message);
    414                     }
    415                     break;
    416                 case INTENT_BATTERY_CHANGED:
    417                     processIntentBatteryChanged((Intent) message.obj);
    418                     break;
    419                 case ROAM_CHANGED:
    420                     processRoamChanged((Boolean) message.obj);
    421                     break;
    422                 case CALL_STATE_CHANGED:
    423                     processCallState((HeadsetCallState) message.obj,
    424                         ((message.arg1 == 1)?true:false));
    425                     break;
    426                 case STACK_EVENT:
    427                     StackEvent event = (StackEvent) message.obj;
    428                     if (DBG) {
    429                         log("event type: " + event.type);
    430                     }
    431                     switch (event.type) {
    432                         case EVENT_TYPE_CONNECTION_STATE_CHANGED:
    433                             removeMessages(CONNECT_TIMEOUT);
    434                             processConnectionEvent(event.valueInt, event.device);
    435                             break;
    436                         default:
    437                             Log.e(TAG, "Unexpected event: " + event.type);
    438                             break;
    439                     }
    440                     break;
    441                 default:
    442                     return NOT_HANDLED;
    443             }
    444             return retValue;
    445         }
    446 
    447         // in Pending state
    448         private void processConnectionEvent(int state, BluetoothDevice device) {
    449             switch (state) {
    450                 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
    451                     if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
    452                         broadcastConnectionState(mCurrentDevice,
    453                                                  BluetoothProfile.STATE_DISCONNECTED,
    454                                                  BluetoothProfile.STATE_DISCONNECTING);
    455                         synchronized (HeadsetStateMachine.this) {
    456                             mCurrentDevice = null;
    457                         }
    458 
    459                         if (mTargetDevice != null) {
    460                             if (!connectHfpNative(getByteAddress(mTargetDevice))) {
    461                                 broadcastConnectionState(mTargetDevice,
    462                                                          BluetoothProfile.STATE_DISCONNECTED,
    463                                                          BluetoothProfile.STATE_CONNECTING);
    464                                 synchronized (HeadsetStateMachine.this) {
    465                                     mTargetDevice = null;
    466                                     transitionTo(mDisconnected);
    467                                 }
    468                             }
    469                         } else {
    470                             synchronized (HeadsetStateMachine.this) {
    471                                 mIncomingDevice = null;
    472                                 transitionTo(mDisconnected);
    473                             }
    474                         }
    475                     } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
    476                         // outgoing connection failed
    477                         broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
    478                                                  BluetoothProfile.STATE_CONNECTING);
    479                         synchronized (HeadsetStateMachine.this) {
    480                             mTargetDevice = null;
    481                             transitionTo(mDisconnected);
    482                         }
    483                     } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
    484                         broadcastConnectionState(mIncomingDevice,
    485                                                  BluetoothProfile.STATE_DISCONNECTED,
    486                                                  BluetoothProfile.STATE_CONNECTING);
    487                         synchronized (HeadsetStateMachine.this) {
    488                             mIncomingDevice = null;
    489                             transitionTo(mDisconnected);
    490                         }
    491                     } else {
    492                         Log.e(TAG, "Unknown device Disconnected: " + device);
    493                     }
    494                     break;
    495             case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
    496                 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
    497                     // disconnection failed
    498                     broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
    499                                              BluetoothProfile.STATE_DISCONNECTING);
    500                     if (mTargetDevice != null) {
    501                         broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
    502                                                  BluetoothProfile.STATE_CONNECTING);
    503                     }
    504                     synchronized (HeadsetStateMachine.this) {
    505                         mTargetDevice = null;
    506                         transitionTo(mConnected);
    507                     }
    508                 } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
    509                     broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED,
    510                                              BluetoothProfile.STATE_CONNECTING);
    511                     synchronized (HeadsetStateMachine.this) {
    512                         mCurrentDevice = mTargetDevice;
    513                         mTargetDevice = null;
    514                         transitionTo(mConnected);
    515                     }
    516                 } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
    517                     broadcastConnectionState(mIncomingDevice, BluetoothProfile.STATE_CONNECTED,
    518                                              BluetoothProfile.STATE_CONNECTING);
    519                     synchronized (HeadsetStateMachine.this) {
    520                         mCurrentDevice = mIncomingDevice;
    521                         mIncomingDevice = null;
    522                         transitionTo(mConnected);
    523                     }
    524                 } else {
    525                     Log.e(TAG, "Unknown device Connected: " + device);
    526                     // something is wrong here, but sync our state with stack
    527                     broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
    528                                              BluetoothProfile.STATE_DISCONNECTED);
    529                     synchronized (HeadsetStateMachine.this) {
    530                         mCurrentDevice = device;
    531                         mTargetDevice = null;
    532                         mIncomingDevice = null;
    533                         transitionTo(mConnected);
    534                     }
    535                 }
    536                 configAudioParameters();
    537                 break;
    538             case HeadsetHalConstants.CONNECTION_STATE_CONNECTING:
    539                 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
    540                     log("current device tries to connect back");
    541                     // TODO(BT) ignore or reject
    542                 } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
    543                     // The stack is connecting to target device or
    544                     // there is an incoming connection from the target device at the same time
    545                     // we already broadcasted the intent, doing nothing here
    546                     if (DBG) {
    547                         log("Stack and target device are connecting");
    548                     }
    549                 }
    550                 else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
    551                     Log.e(TAG, "Another connecting event on the incoming device");
    552                 } else {
    553                     // We get an incoming connecting request while Pending
    554                     // TODO(BT) is stack handing this case? let's ignore it for now
    555                     log("Incoming connection while pending, ignore");
    556                 }
    557                 break;
    558             case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING:
    559                 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
    560                     // we already broadcasted the intent, doing nothing here
    561                     if (DBG) {
    562                         log("stack is disconnecting mCurrentDevice");
    563                     }
    564                 } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
    565                     Log.e(TAG, "TargetDevice is getting disconnected");
    566                 } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
    567                     Log.e(TAG, "IncomingDevice is getting disconnected");
    568                 } else {
    569                     Log.e(TAG, "Disconnecting unknow device: " + device);
    570                 }
    571                 break;
    572             default:
    573                 Log.e(TAG, "Incorrect state: " + state);
    574                 break;
    575             }
    576         }
    577 
    578     }
    579 
    580     private class Connected extends State {
    581         @Override
    582         public void enter() {
    583             log("Enter Connected: " + getCurrentMessage().what);
    584         }
    585 
    586         @Override
    587         public boolean processMessage(Message message) {
    588             log("Connected process message: " + message.what);
    589             if (DBG) {
    590                 if (mCurrentDevice == null) {
    591                     log("ERROR: mCurrentDevice is null in Connected");
    592                     return NOT_HANDLED;
    593                 }
    594             }
    595 
    596             boolean retValue = HANDLED;
    597             switch(message.what) {
    598                 case CONNECT:
    599                 {
    600                     BluetoothDevice device = (BluetoothDevice) message.obj;
    601                     if (mCurrentDevice.equals(device)) {
    602                         break;
    603                     }
    604 
    605                     broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
    606                                    BluetoothProfile.STATE_DISCONNECTED);
    607                     if (!disconnectHfpNative(getByteAddress(mCurrentDevice))) {
    608                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
    609                                        BluetoothProfile.STATE_CONNECTING);
    610                         break;
    611                     }
    612 
    613                     synchronized (HeadsetStateMachine.this) {
    614                         mTargetDevice = device;
    615                         transitionTo(mPending);
    616                     }
    617                 }
    618                     break;
    619                 case DISCONNECT:
    620                 {
    621                     BluetoothDevice device = (BluetoothDevice) message.obj;
    622                     if (!mCurrentDevice.equals(device)) {
    623                         break;
    624                     }
    625                     broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING,
    626                                    BluetoothProfile.STATE_CONNECTED);
    627                     if (!disconnectHfpNative(getByteAddress(device))) {
    628                         broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
    629                                        BluetoothProfile.STATE_DISCONNECTED);
    630                         break;
    631                     }
    632                     transitionTo(mPending);
    633                 }
    634                     break;
    635                 case CONNECT_AUDIO:
    636                     // TODO(BT) when failure, broadcast audio connecting to disconnected intent
    637                     //          check if device matches mCurrentDevice
    638                     connectAudioNative(getByteAddress(mCurrentDevice));
    639                     break;
    640                 case VOICE_RECOGNITION_START:
    641                     processLocalVrEvent(HeadsetHalConstants.VR_STATE_STARTED);
    642                     break;
    643                 case VOICE_RECOGNITION_STOP:
    644                     processLocalVrEvent(HeadsetHalConstants.VR_STATE_STOPPED);
    645                     break;
    646                 case CALL_STATE_CHANGED:
    647                     processCallState((HeadsetCallState) message.obj, ((message.arg1==1)?true:false));
    648                     break;
    649                 case INTENT_BATTERY_CHANGED:
    650                     processIntentBatteryChanged((Intent) message.obj);
    651                     break;
    652                 case ROAM_CHANGED:
    653                     processRoamChanged((Boolean) message.obj);
    654                     break;
    655                 case DEVICE_STATE_CHANGED:
    656                     processDeviceStateChanged((HeadsetDeviceState) message.obj);
    657                     break;
    658                 case SEND_CCLC_RESPONSE:
    659                     processSendClccResponse((HeadsetClccResponse) message.obj);
    660                     break;
    661                 case DIALING_OUT_TIMEOUT:
    662                     if (mDialingOut) {
    663                         mDialingOut= false;
    664                         atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
    665                     }
    666                     break;
    667                 case VIRTUAL_CALL_START:
    668                     initiateScoUsingVirtualVoiceCall();
    669                     break;
    670                 case VIRTUAL_CALL_STOP:
    671                     terminateScoUsingVirtualVoiceCall();
    672                     break;
    673                 case START_VR_TIMEOUT:
    674                     if (mWaitingForVoiceRecognition) {
    675                         mWaitingForVoiceRecognition = false;
    676                         Log.e(TAG, "Timeout waiting for voice recognition to start");
    677                         atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
    678                     }
    679                     break;
    680                 case STACK_EVENT:
    681                     StackEvent event = (StackEvent) message.obj;
    682                     if (DBG) {
    683                         log("event type: " + event.type);
    684                     }
    685                     switch (event.type) {
    686                         case EVENT_TYPE_CONNECTION_STATE_CHANGED:
    687                             processConnectionEvent(event.valueInt, event.device);
    688                             break;
    689                         case EVENT_TYPE_AUDIO_STATE_CHANGED:
    690                             processAudioEvent(event.valueInt, event.device);
    691                             break;
    692                         case EVENT_TYPE_VR_STATE_CHANGED:
    693                             processVrEvent(event.valueInt);
    694                             break;
    695                         case EVENT_TYPE_ANSWER_CALL:
    696                             // TODO(BT) could answer call happen on Connected state?
    697                             processAnswerCall();
    698                             break;
    699                         case EVENT_TYPE_HANGUP_CALL:
    700                             // TODO(BT) could hangup call happen on Connected state?
    701                             processHangupCall();
    702                             break;
    703                         case EVENT_TYPE_VOLUME_CHANGED:
    704                             processVolumeEvent(event.valueInt, event.valueInt2);
    705                             break;
    706                         case EVENT_TYPE_DIAL_CALL:
    707                             processDialCall(event.valueString);
    708                             break;
    709                         case EVENT_TYPE_SEND_DTMF:
    710                             processSendDtmf(event.valueInt);
    711                             break;
    712                         case EVENT_TYPE_NOICE_REDUCTION:
    713                             processNoiceReductionEvent(event.valueInt);
    714                             break;
    715                         case EVENT_TYPE_AT_CHLD:
    716                             processAtChld(event.valueInt);
    717                             break;
    718                         case EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST:
    719                             processSubscriberNumberRequest();
    720                             break;
    721                         case EVENT_TYPE_AT_CIND:
    722                             processAtCind();
    723                             break;
    724                         case EVENT_TYPE_AT_COPS:
    725                             processAtCops();
    726                             break;
    727                         case EVENT_TYPE_AT_CLCC:
    728                             processAtClcc();
    729                             break;
    730                         case EVENT_TYPE_UNKNOWN_AT:
    731                             processUnknownAt(event.valueString);
    732                             break;
    733                         case EVENT_TYPE_KEY_PRESSED:
    734                             processKeyPressed();
    735                             break;
    736                         default:
    737                             Log.e(TAG, "Unknown stack event: " + event.type);
    738                             break;
    739                     }
    740                     break;
    741                 default:
    742                     return NOT_HANDLED;
    743             }
    744             return retValue;
    745         }
    746 
    747         // in Connected state
    748         private void processConnectionEvent(int state, BluetoothDevice device) {
    749             switch (state) {
    750                 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
    751                     if (mCurrentDevice.equals(device)) {
    752                         broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
    753                                                  BluetoothProfile.STATE_CONNECTED);
    754                         synchronized (HeadsetStateMachine.this) {
    755                             mCurrentDevice = null;
    756                             transitionTo(mDisconnected);
    757                         }
    758                     } else {
    759                         Log.e(TAG, "Disconnected from unknown device: " + device);
    760                     }
    761                     break;
    762                 case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED:
    763                     processSlcConnected();
    764                     break;
    765               default:
    766                   Log.e(TAG, "Connection State Device: " + device + " bad state: " + state);
    767                   break;
    768             }
    769         }
    770 
    771         // in Connected state
    772         private void processAudioEvent(int state, BluetoothDevice device) {
    773             if (!mCurrentDevice.equals(device)) {
    774                 Log.e(TAG, "Audio changed on disconnected device: " + device);
    775                 return;
    776             }
    777 
    778             switch (state) {
    779                 case HeadsetHalConstants.AUDIO_STATE_CONNECTED:
    780                     // TODO(BT) should I save the state for next broadcast as the prevState?
    781                     mAudioState = BluetoothHeadset.STATE_AUDIO_CONNECTED;
    782                     mAudioManager.setBluetoothScoOn(true);
    783                     broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_CONNECTED,
    784                                         BluetoothHeadset.STATE_AUDIO_CONNECTING);
    785                     transitionTo(mAudioOn);
    786                     break;
    787                 case HeadsetHalConstants.AUDIO_STATE_CONNECTING:
    788                     mAudioState = BluetoothHeadset.STATE_AUDIO_CONNECTING;
    789                     broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_CONNECTING,
    790                                         BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
    791                     break;
    792                     // TODO(BT) process other states
    793                 default:
    794                     Log.e(TAG, "Audio State Device: " + device + " bad state: " + state);
    795                     break;
    796             }
    797         }
    798 
    799         private void processSlcConnected() {
    800             if (mPhoneProxy != null) {
    801                 try {
    802                     // start phone state listener here, instead of on disconnected exit()
    803                     // On BT off, exitting SM sends a SM exit() call which incorrectly forces
    804                     // a listenForPhoneState(true).
    805                     // Additionally, no indicator updates should be sent prior to SLC setup
    806                     mPhoneState.listenForPhoneState(true);
    807                     mPhoneProxy.queryPhoneState();
    808                 } catch (RemoteException e) {
    809                     Log.e(TAG, Log.getStackTraceString(new Throwable()));
    810                 }
    811             } else {
    812                 Log.e(TAG, "Handsfree phone proxy null for query phone state");
    813             }
    814 
    815         }
    816     }
    817 
    818     private class AudioOn extends State {
    819 
    820         @Override
    821         public void enter() {
    822             log("Enter AudioOn: " + getCurrentMessage().what);
    823         }
    824 
    825         @Override
    826         public boolean processMessage(Message message) {
    827             log("AudioOn process message: " + message.what);
    828             if (DBG) {
    829                 if (mCurrentDevice == null) {
    830                     log("ERROR: mCurrentDevice is null in AudioOn");
    831                     return NOT_HANDLED;
    832                 }
    833             }
    834 
    835             boolean retValue = HANDLED;
    836             switch(message.what) {
    837                 case DISCONNECT:
    838                 {
    839                     BluetoothDevice device = (BluetoothDevice) message.obj;
    840                     if (!mCurrentDevice.equals(device)) {
    841                         break;
    842                     }
    843                     deferMessage(obtainMessage(DISCONNECT, message.obj));
    844                 }
    845                 // fall through
    846                 case DISCONNECT_AUDIO:
    847                     if (disconnectAudioNative(getByteAddress(mCurrentDevice))) {
    848                         mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
    849                         mAudioManager.setBluetoothScoOn(false);
    850                         broadcastAudioState(mCurrentDevice, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
    851                                             BluetoothHeadset.STATE_AUDIO_CONNECTED);
    852                     }
    853                     break;
    854                 case VOICE_RECOGNITION_START:
    855                     processLocalVrEvent(HeadsetHalConstants.VR_STATE_STARTED);
    856                     break;
    857                 case VOICE_RECOGNITION_STOP:
    858                     processLocalVrEvent(HeadsetHalConstants.VR_STATE_STOPPED);
    859                     break;
    860                 case INTENT_SCO_VOLUME_CHANGED:
    861                     processIntentScoVolume((Intent) message.obj);
    862                     break;
    863                 case CALL_STATE_CHANGED:
    864                     processCallState((HeadsetCallState) message.obj, ((message.arg1 == 1)?true:false));
    865                     break;
    866                 case INTENT_BATTERY_CHANGED:
    867                     processIntentBatteryChanged((Intent) message.obj);
    868                     break;
    869                 case ROAM_CHANGED:
    870                     processRoamChanged((Boolean) message.obj);
    871                     break;
    872                 case DEVICE_STATE_CHANGED:
    873                     processDeviceStateChanged((HeadsetDeviceState) message.obj);
    874                     break;
    875                 case SEND_CCLC_RESPONSE:
    876                     processSendClccResponse((HeadsetClccResponse) message.obj);
    877                     break;
    878 
    879                 case VIRTUAL_CALL_START:
    880                     initiateScoUsingVirtualVoiceCall();
    881                     break;
    882                 case VIRTUAL_CALL_STOP:
    883                     terminateScoUsingVirtualVoiceCall();
    884                     break;
    885 
    886                 case DIALING_OUT_TIMEOUT:
    887                     if (mDialingOut) {
    888                         mDialingOut= false;
    889                         atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
    890                     }
    891                     break;
    892                 case START_VR_TIMEOUT:
    893                     if (mWaitingForVoiceRecognition) {
    894                         mWaitingForVoiceRecognition = false;
    895                         Log.e(TAG, "Timeout waiting for voice recognition to start");
    896                         atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
    897                     }
    898                     break;
    899                 case STACK_EVENT:
    900                     StackEvent event = (StackEvent) message.obj;
    901                     if (DBG) {
    902                         log("event type: " + event.type);
    903                     }
    904                     switch (event.type) {
    905                         case EVENT_TYPE_CONNECTION_STATE_CHANGED:
    906                             processConnectionEvent(event.valueInt, event.device);
    907                             break;
    908                         case EVENT_TYPE_AUDIO_STATE_CHANGED:
    909                             processAudioEvent(event.valueInt, event.device);
    910                             break;
    911                         case EVENT_TYPE_VR_STATE_CHANGED:
    912                             processVrEvent(event.valueInt);
    913                             break;
    914                         case EVENT_TYPE_ANSWER_CALL:
    915                             processAnswerCall();
    916                             break;
    917                         case EVENT_TYPE_HANGUP_CALL:
    918                             processHangupCall();
    919                             break;
    920                         case EVENT_TYPE_VOLUME_CHANGED:
    921                             processVolumeEvent(event.valueInt, event.valueInt2);
    922                             break;
    923                         case EVENT_TYPE_DIAL_CALL:
    924                             processDialCall(event.valueString);
    925                             break;
    926                         case EVENT_TYPE_SEND_DTMF:
    927                             processSendDtmf(event.valueInt);
    928                             break;
    929                         case EVENT_TYPE_NOICE_REDUCTION:
    930                             processNoiceReductionEvent(event.valueInt);
    931                             break;
    932                         case EVENT_TYPE_AT_CHLD:
    933                             processAtChld(event.valueInt);
    934                             break;
    935                         case EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST:
    936                             processSubscriberNumberRequest();
    937                             break;
    938                         case EVENT_TYPE_AT_CIND:
    939                             processAtCind();
    940                             break;
    941                         case EVENT_TYPE_AT_COPS:
    942                             processAtCops();
    943                             break;
    944                         case EVENT_TYPE_AT_CLCC:
    945                             processAtClcc();
    946                             break;
    947                         case EVENT_TYPE_UNKNOWN_AT:
    948                             processUnknownAt(event.valueString);
    949                             break;
    950                         case EVENT_TYPE_KEY_PRESSED:
    951                             processKeyPressed();
    952                             break;
    953                         default:
    954                             Log.e(TAG, "Unknown stack event: " + event.type);
    955                             break;
    956                     }
    957                     break;
    958                 default:
    959                     return NOT_HANDLED;
    960             }
    961             return retValue;
    962         }
    963 
    964         // in AudioOn state. Some headsets disconnect RFCOMM prior to SCO down. Handle this
    965         private void processConnectionEvent(int state, BluetoothDevice device) {
    966             switch (state) {
    967                 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
    968                     if (mCurrentDevice.equals(device)) {
    969                         processAudioEvent (HeadsetHalConstants.AUDIO_STATE_DISCONNECTED, device);
    970                         broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
    971                                                  BluetoothProfile.STATE_CONNECTED);
    972                         synchronized (HeadsetStateMachine.this) {
    973                             mCurrentDevice = null;
    974                             transitionTo(mDisconnected);
    975                         }
    976                     } else {
    977                         Log.e(TAG, "Disconnected from unknown device: " + device);
    978                     }
    979                     break;
    980               default:
    981                   Log.e(TAG, "Connection State Device: " + device + " bad state: " + state);
    982                   break;
    983             }
    984         }
    985 
    986         // in AudioOn state
    987         private void processAudioEvent(int state, BluetoothDevice device) {
    988             if (!mCurrentDevice.equals(device)) {
    989                 Log.e(TAG, "Audio changed on disconnected device: " + device);
    990                 return;
    991             }
    992 
    993             switch (state) {
    994                 case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
    995                     if (mAudioState != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
    996                         mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
    997                         mAudioManager.setBluetoothScoOn(false);
    998                         broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
    999                                             BluetoothHeadset.STATE_AUDIO_CONNECTED);
   1000                     }
   1001                     transitionTo(mConnected);
   1002                     break;
   1003                 case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING:
   1004                     // TODO(BT) adding STATE_AUDIO_DISCONNECTING in BluetoothHeadset?
   1005                     //broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_DISCONNECTING,
   1006                     //                    BluetoothHeadset.STATE_AUDIO_CONNECTED);
   1007                     break;
   1008                 default:
   1009                     Log.e(TAG, "Audio State Device: " + device + " bad state: " + state);
   1010                     break;
   1011             }
   1012         }
   1013 
   1014         private void processIntentScoVolume(Intent intent) {
   1015             int volumeValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
   1016             if (mPhoneState.getSpeakerVolume() != volumeValue) {
   1017                 mPhoneState.setSpeakerVolume(volumeValue);
   1018                 setVolumeNative(HeadsetHalConstants.VOLUME_TYPE_SPK, volumeValue);
   1019             }
   1020         }
   1021     }
   1022 
   1023     private ServiceConnection mConnection = new ServiceConnection() {
   1024         public void onServiceConnected(ComponentName className, IBinder service) {
   1025             if (DBG) Log.d(TAG, "Proxy object connected");
   1026             mPhoneProxy = IBluetoothHeadsetPhone.Stub.asInterface(service);
   1027         }
   1028 
   1029         public void onServiceDisconnected(ComponentName className) {
   1030             if (DBG) Log.d(TAG, "Proxy object disconnected");
   1031             mPhoneProxy = null;
   1032         }
   1033     };
   1034 
   1035     // HFP Connection state of the device could be changed by the state machine
   1036     // in separate thread while this method is executing.
   1037     int getConnectionState(BluetoothDevice device) {
   1038         if (getCurrentState() == mDisconnected) {
   1039             return BluetoothProfile.STATE_DISCONNECTED;
   1040         }
   1041 
   1042         synchronized (this) {
   1043             IState currentState = getCurrentState();
   1044             if (currentState == mPending) {
   1045                 if ((mTargetDevice != null) && mTargetDevice.equals(device)) {
   1046                     return BluetoothProfile.STATE_CONNECTING;
   1047                 }
   1048                 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
   1049                     return BluetoothProfile.STATE_DISCONNECTING;
   1050                 }
   1051                 if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) {
   1052                     return BluetoothProfile.STATE_CONNECTING; // incoming connection
   1053                 }
   1054                 return BluetoothProfile.STATE_DISCONNECTED;
   1055             }
   1056 
   1057             if (currentState == mConnected || currentState == mAudioOn) {
   1058                 if (mCurrentDevice.equals(device)) {
   1059                     return BluetoothProfile.STATE_CONNECTED;
   1060                 }
   1061                 return BluetoothProfile.STATE_DISCONNECTED;
   1062             } else {
   1063                 Log.e(TAG, "Bad currentState: " + currentState);
   1064                 return BluetoothProfile.STATE_DISCONNECTED;
   1065             }
   1066         }
   1067     }
   1068 
   1069     List<BluetoothDevice> getConnectedDevices() {
   1070         List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
   1071         synchronized(this) {
   1072             if (isConnected()) {
   1073                 devices.add(mCurrentDevice);
   1074             }
   1075         }
   1076         return devices;
   1077     }
   1078 
   1079     boolean isAudioOn() {
   1080         return (getCurrentState() == mAudioOn);
   1081     }
   1082 
   1083     boolean isAudioConnected(BluetoothDevice device) {
   1084         synchronized(this) {
   1085 
   1086             /*  Additional check for audio state included for the case when PhoneApp queries
   1087             Bluetooth Audio state, before we receive the close event from the stack for the
   1088             sco disconnect issued in AudioOn state. This was causing a mismatch in the
   1089             Incall screen UI. */
   1090 
   1091             if (getCurrentState() == mAudioOn && mCurrentDevice.equals(device)
   1092                 && mAudioState != BluetoothHeadset.STATE_AUDIO_DISCONNECTED)
   1093             {
   1094                 return true;
   1095             }
   1096         }
   1097         return false;
   1098     }
   1099 
   1100     int getAudioState(BluetoothDevice device) {
   1101         synchronized(this) {
   1102             if (mCurrentDevice == null || !mCurrentDevice.equals(device)) {
   1103                 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
   1104             }
   1105         }
   1106         return mAudioState;
   1107     }
   1108 
   1109     private void processVrEvent(int state) {
   1110         Log.d(TAG, "processVrEvent: state=" + state + " mVoiceRecognitionStarted: " +
   1111             mVoiceRecognitionStarted + " mWaitingforVoiceRecognition: " + mWaitingForVoiceRecognition +
   1112             " isInCall: " + isInCall());
   1113         if (state == HeadsetHalConstants.VR_STATE_STARTED) {
   1114             if (!mVoiceRecognitionStarted &&
   1115                 !isVirtualCallInProgress() &&
   1116                 !isInCall())
   1117             {
   1118                 try {
   1119                     mService.startActivity(sVoiceCommandIntent);
   1120                 } catch (ActivityNotFoundException e) {
   1121                     atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
   1122                     return;
   1123                 }
   1124                 expectVoiceRecognition();
   1125             }
   1126         } else if (state == HeadsetHalConstants.VR_STATE_STOPPED) {
   1127             if (mVoiceRecognitionStarted || mWaitingForVoiceRecognition)
   1128             {
   1129                 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
   1130                 mVoiceRecognitionStarted = false;
   1131                 mWaitingForVoiceRecognition = false;
   1132                 if (!isInCall()) {
   1133                     disconnectAudioNative(getByteAddress(mCurrentDevice));
   1134                     mAudioManager.setParameters("A2dpSuspended=false");
   1135                 }
   1136             }
   1137             else
   1138             {
   1139                 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
   1140             }
   1141         } else {
   1142             Log.e(TAG, "Bad Voice Recognition state: " + state);
   1143         }
   1144     }
   1145 
   1146     private void processLocalVrEvent(int state)
   1147     {
   1148         if (state == HeadsetHalConstants.VR_STATE_STARTED)
   1149         {
   1150             boolean needAudio = true;
   1151             if (mVoiceRecognitionStarted || isInCall())
   1152             {
   1153                 Log.e(TAG, "Voice recognition started when call is active. isInCall:" + isInCall() +
   1154                     " mVoiceRecognitionStarted: " + mVoiceRecognitionStarted);
   1155                 return;
   1156             }
   1157             mVoiceRecognitionStarted = true;
   1158 
   1159             if (mWaitingForVoiceRecognition)
   1160             {
   1161                 Log.d(TAG, "Voice recognition started successfully");
   1162                 mWaitingForVoiceRecognition = false;
   1163                 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
   1164                 removeMessages(START_VR_TIMEOUT);
   1165             }
   1166             else
   1167             {
   1168                 Log.d(TAG, "Voice recognition started locally");
   1169                 needAudio = startVoiceRecognitionNative();
   1170             }
   1171 
   1172             if (needAudio && !isAudioOn())
   1173             {
   1174                 Log.d(TAG, "Initiating audio connection for Voice Recognition");
   1175                 // At this stage, we need to be sure that AVDTP is not streaming. This is needed
   1176                 // to be compliant with the AV+HFP Whitepaper as we cannot have A2DP in
   1177                 // streaming state while a SCO connection is established.
   1178                 // This is needed for VoiceDial scenario alone and not for
   1179                 // incoming call/outgoing call scenarios as the phone enters MODE_RINGTONE
   1180                 // or MODE_IN_CALL which shall automatically suspend the AVDTP stream if needed.
   1181                 // Whereas for VoiceDial we want to activate the SCO connection but we are still
   1182                 // in MODE_NORMAL and hence the need to explicitly suspend the A2DP stream
   1183                 mAudioManager.setParameters("A2dpSuspended=true");
   1184                 connectAudioNative(getByteAddress(mCurrentDevice));
   1185             }
   1186 
   1187             if (mStartVoiceRecognitionWakeLock.isHeld()) {
   1188                 mStartVoiceRecognitionWakeLock.release();
   1189             }
   1190         }
   1191         else
   1192         {
   1193             Log.d(TAG, "Voice Recognition stopped. mVoiceRecognitionStarted: " + mVoiceRecognitionStarted +
   1194                 " mWaitingForVoiceRecognition: " + mWaitingForVoiceRecognition);
   1195             if (mVoiceRecognitionStarted || mWaitingForVoiceRecognition)
   1196             {
   1197                 mVoiceRecognitionStarted = false;
   1198                 mWaitingForVoiceRecognition = false;
   1199 
   1200                 if (stopVoiceRecognitionNative() && !isInCall()) {
   1201                     disconnectAudioNative(getByteAddress(mCurrentDevice));
   1202                     mAudioManager.setParameters("A2dpSuspended=false");
   1203                 }
   1204             }
   1205         }
   1206     }
   1207 
   1208     private synchronized void expectVoiceRecognition() {
   1209         mWaitingForVoiceRecognition = true;
   1210         sendMessageDelayed(START_VR_TIMEOUT, START_VR_TIMEOUT_VALUE);
   1211         if (!mStartVoiceRecognitionWakeLock.isHeld()) {
   1212             mStartVoiceRecognitionWakeLock.acquire(START_VR_TIMEOUT_VALUE);
   1213         }
   1214     }
   1215 
   1216     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
   1217         List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
   1218         Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
   1219         int connectionState;
   1220         synchronized (this) {
   1221             for (BluetoothDevice device : bondedDevices) {
   1222                 ParcelUuid[] featureUuids = device.getUuids();
   1223                 if (!BluetoothUuid.containsAnyUuid(featureUuids, HEADSET_UUIDS)) {
   1224                     continue;
   1225                 }
   1226                 connectionState = getConnectionState(device);
   1227                 for(int i = 0; i < states.length; i++) {
   1228                     if (connectionState == states[i]) {
   1229                         deviceList.add(device);
   1230                     }
   1231                 }
   1232             }
   1233         }
   1234         return deviceList;
   1235     }
   1236 
   1237     // This method does not check for error conditon (newState == prevState)
   1238     private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
   1239         log("Connection state " + device + ": " + prevState + "->" + newState);
   1240         if(prevState == BluetoothProfile.STATE_CONNECTED) {
   1241             // Headset is disconnecting, stop Virtual call if active.
   1242             terminateScoUsingVirtualVoiceCall();
   1243         }
   1244 
   1245         /* Notifying the connection state change of the profile before sending the intent for
   1246            connection state change, as it was causing a race condition, with the UI not being
   1247            updated with the correct connection state. */
   1248         mService.notifyProfileConnectionStateChanged(device, BluetoothProfile.HEADSET,
   1249                                                      newState, prevState);
   1250         Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
   1251         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
   1252         intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
   1253         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
   1254         mService.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM);
   1255     }
   1256 
   1257     private void broadcastAudioState(BluetoothDevice device, int newState, int prevState) {
   1258         if(prevState == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
   1259             // When SCO gets disconnected during call transfer, Virtual call
   1260             //needs to be cleaned up.So call terminateScoUsingVirtualVoiceCall.
   1261             terminateScoUsingVirtualVoiceCall();
   1262         }
   1263         Intent intent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
   1264         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
   1265         intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
   1266         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
   1267         mService.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM);
   1268         log("Audio state " + device + ": " + prevState + "->" + newState);
   1269     }
   1270 
   1271     /*
   1272      * Put the AT command, company ID, arguments, and device in an Intent and broadcast it.
   1273      */
   1274     private void broadcastVendorSpecificEventIntent(String command,
   1275                                                     int companyId,
   1276                                                     int commandType,
   1277                                                     Object[] arguments,
   1278                                                     BluetoothDevice device) {
   1279         log("broadcastVendorSpecificEventIntent(" + command + ")");
   1280         Intent intent =
   1281                 new Intent(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT);
   1282         intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD, command);
   1283         intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE,
   1284                         commandType);
   1285         // assert: all elements of args are Serializable
   1286         intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS, arguments);
   1287         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
   1288 
   1289         intent.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY
   1290             + "." + Integer.toString(companyId));
   1291 
   1292         mService.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM);
   1293     }
   1294 
   1295     private void configAudioParameters()
   1296     {
   1297         // Reset NREC on connect event. Headset will override later
   1298         mAudioManager.setParameters(HEADSET_NAME + "=" + getCurrentDeviceName() + ";" +
   1299                                     HEADSET_NREC + "=on");
   1300     }
   1301 
   1302     private String parseUnknownAt(String atString)
   1303     {
   1304         StringBuilder atCommand = new StringBuilder(atString.length());
   1305         String result = null;
   1306 
   1307         for (int i = 0; i < atString.length(); i++) {
   1308             char c = atString.charAt(i);
   1309             if (c == '"') {
   1310                 int j = atString.indexOf('"', i + 1 );  // search for closing "
   1311                 if (j == -1) {  // unmatched ", insert one.
   1312                     atCommand.append(atString.substring(i, atString.length()));
   1313                     atCommand.append('"');
   1314                     break;
   1315                 }
   1316                 atCommand.append(atString.substring(i, j + 1));
   1317                 i = j;
   1318             } else if (c != ' ') {
   1319                 atCommand.append(Character.toUpperCase(c));
   1320             }
   1321         }
   1322         result = atCommand.toString();
   1323         return result;
   1324     }
   1325 
   1326     private int getAtCommandType(String atCommand)
   1327     {
   1328         int commandType = mPhonebook.TYPE_UNKNOWN;
   1329         String atString = null;
   1330         atCommand = atCommand.trim();
   1331         if (atCommand.length() > 5)
   1332         {
   1333             atString = atCommand.substring(5);
   1334             if (atString.startsWith("?"))     // Read
   1335                 commandType = mPhonebook.TYPE_READ;
   1336             else if (atString.startsWith("=?"))   // Test
   1337                 commandType = mPhonebook.TYPE_TEST;
   1338             else if (atString.startsWith("="))   // Set
   1339                 commandType = mPhonebook.TYPE_SET;
   1340             else
   1341                 commandType = mPhonebook.TYPE_UNKNOWN;
   1342         }
   1343         return commandType;
   1344     }
   1345 
   1346     /* Method to check if Virtual Call in Progress */
   1347     private boolean isVirtualCallInProgress() {
   1348         return mVirtualCallStarted;
   1349     }
   1350 
   1351     void setVirtualCallInProgress(boolean state) {
   1352         mVirtualCallStarted = state;
   1353     }
   1354 
   1355     /* NOTE: Currently the VirtualCall API does not support handling of
   1356     call transfers. If it is initiated from the handsfree device,
   1357     HeadsetStateMachine will end the virtual call by calling
   1358     terminateScoUsingVirtualVoiceCall() in broadcastAudioState() */
   1359     synchronized boolean initiateScoUsingVirtualVoiceCall() {
   1360         if (DBG) log("initiateScoUsingVirtualVoiceCall: Received");
   1361         // 1. Check if the SCO state is idle
   1362         if (isInCall() || mVoiceRecognitionStarted) {
   1363             Log.e(TAG, "initiateScoUsingVirtualVoiceCall: Call in progress.");
   1364             return false;
   1365         }
   1366 
   1367         // 2. Send virtual phone state changed to initialize SCO
   1368         processCallState(new HeadsetCallState(0, 0,
   1369             HeadsetHalConstants.CALL_STATE_DIALING, "", 0), true);
   1370         processCallState(new HeadsetCallState(0, 0,
   1371             HeadsetHalConstants.CALL_STATE_ALERTING, "", 0), true);
   1372         processCallState(new HeadsetCallState(1, 0,
   1373             HeadsetHalConstants.CALL_STATE_IDLE, "", 0), true);
   1374         setVirtualCallInProgress(true);
   1375         // Done
   1376         if (DBG) log("initiateScoUsingVirtualVoiceCall: Done");
   1377         return true;
   1378     }
   1379 
   1380     synchronized boolean terminateScoUsingVirtualVoiceCall() {
   1381         if (DBG) log("terminateScoUsingVirtualVoiceCall: Received");
   1382 
   1383         if (!isVirtualCallInProgress()) {
   1384             Log.e(TAG, "terminateScoUsingVirtualVoiceCall:"+
   1385                 "No present call to terminate");
   1386             return false;
   1387         }
   1388 
   1389         // 2. Send virtual phone state changed to close SCO
   1390         processCallState(new HeadsetCallState(0, 0,
   1391             HeadsetHalConstants.CALL_STATE_IDLE, "", 0), true);
   1392         setVirtualCallInProgress(false);
   1393         // Done
   1394         if (DBG) log("terminateScoUsingVirtualVoiceCall: Done");
   1395         return true;
   1396     }
   1397 
   1398     private void processAnswerCall() {
   1399         if (mPhoneProxy != null) {
   1400             try {
   1401                 mPhoneProxy.answerCall();
   1402             } catch (RemoteException e) {
   1403                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
   1404             }
   1405         } else {
   1406             Log.e(TAG, "Handsfree phone proxy null for answering call");
   1407         }
   1408     }
   1409 
   1410     private void processHangupCall() {
   1411         // Close the virtual call if active. Virtual call should be
   1412         // terminated for CHUP callback event
   1413         if (isVirtualCallInProgress()) {
   1414             terminateScoUsingVirtualVoiceCall();
   1415         } else {
   1416             if (mPhoneProxy != null) {
   1417                 try {
   1418                     mPhoneProxy.hangupCall();
   1419                 } catch (RemoteException e) {
   1420                     Log.e(TAG, Log.getStackTraceString(new Throwable()));
   1421                 }
   1422             } else {
   1423                 Log.e(TAG, "Handsfree phone proxy null for hanging up call");
   1424             }
   1425         }
   1426     }
   1427 
   1428     private void processDialCall(String number) {
   1429         String dialNumber;
   1430         if ((number == null) || (number.length() == 0)) {
   1431             dialNumber = mPhonebook.getLastDialledNumber();
   1432             if (dialNumber == null) {
   1433                 if (DBG) log("processDialCall, last dial number null");
   1434                 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
   1435                 return;
   1436             }
   1437         } else if (number.charAt(0) == '>') {
   1438             // Yuck - memory dialling requested.
   1439             // Just dial last number for now
   1440             if (number.startsWith(">9999")) {   // for PTS test
   1441                 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
   1442                 return;
   1443             }
   1444             if (DBG) log("processDialCall, memory dial do last dial for now");
   1445             dialNumber = mPhonebook.getLastDialledNumber();
   1446             if (dialNumber == null) {
   1447                 if (DBG) log("processDialCall, last dial number null");
   1448                 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
   1449                 return;
   1450             }
   1451         } else {
   1452             // Remove trailing ';'
   1453             if (number.charAt(number.length() - 1) == ';') {
   1454                 number = number.substring(0, number.length() - 1);
   1455             }
   1456 
   1457             dialNumber = PhoneNumberUtils.convertPreDial(number);
   1458         }
   1459         // Check for virtual call to terminate before sending Call Intent
   1460         terminateScoUsingVirtualVoiceCall();
   1461 
   1462         Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
   1463                                    Uri.fromParts(SCHEME_TEL, dialNumber, null));
   1464         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   1465         mService.startActivity(intent);
   1466         // TODO(BT) continue send OK reults code after call starts
   1467         //          hold wait lock, start a timer, set wait call flag
   1468         //          Get call started indication from bluetooth phone
   1469         mDialingOut = true;
   1470         sendMessageDelayed(DIALING_OUT_TIMEOUT, DIALING_OUT_TIMEOUT_VALUE);
   1471     }
   1472 
   1473     private void processVolumeEvent(int volumeType, int volume) {
   1474         if (volumeType == HeadsetHalConstants.VOLUME_TYPE_SPK) {
   1475             mPhoneState.setSpeakerVolume(volume);
   1476             int flag = (getCurrentState() == mAudioOn) ? AudioManager.FLAG_SHOW_UI : 0;
   1477             mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, volume, flag);
   1478         } else if (volumeType == HeadsetHalConstants.VOLUME_TYPE_MIC) {
   1479             mPhoneState.setMicVolume(volume);
   1480         } else {
   1481             Log.e(TAG, "Bad voluem type: " + volumeType);
   1482         }
   1483     }
   1484 
   1485     private void processSendDtmf(int dtmf) {
   1486         if (mPhoneProxy != null) {
   1487             try {
   1488                 mPhoneProxy.sendDtmf(dtmf);
   1489             } catch (RemoteException e) {
   1490                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
   1491             }
   1492         } else {
   1493             Log.e(TAG, "Handsfree phone proxy null for sending DTMF");
   1494         }
   1495     }
   1496 
   1497     private void processCallState(HeadsetCallState callState) {
   1498         processCallState(callState, false);
   1499     }
   1500 
   1501     private void processCallState(HeadsetCallState callState,
   1502         boolean isVirtualCall) {
   1503         mPhoneState.setNumActiveCall(callState.mNumActive);
   1504         mPhoneState.setNumHeldCall(callState.mNumHeld);
   1505         mPhoneState.setCallState(callState.mCallState);
   1506         if (mDialingOut && callState.mCallState ==
   1507             HeadsetHalConstants.CALL_STATE_DIALING) {
   1508                 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
   1509                 removeMessages(DIALING_OUT_TIMEOUT);
   1510                 mDialingOut = false;
   1511         }
   1512         log("mNumActive: " + callState.mNumActive + " mNumHeld: " +
   1513             callState.mNumHeld +" mCallState: " + callState.mCallState);
   1514         log("mNumber: " + callState.mNumber + " mType: " + callState.mType);
   1515         if(!isVirtualCall) {
   1516             /* Not a Virtual call request. End the virtual call, if running,
   1517             before sending phoneStateChangeNative to BTIF */
   1518             terminateScoUsingVirtualVoiceCall();
   1519         }
   1520         if (getCurrentState() != mDisconnected) {
   1521             phoneStateChangeNative(callState.mNumActive, callState.mNumHeld,
   1522                 callState.mCallState, callState.mNumber, callState.mType);
   1523         }
   1524     }
   1525 
   1526     // enable 1 enable noice reduction
   1527     //        0 disable noice reduction
   1528     private void processNoiceReductionEvent(int enable) {
   1529         if (enable == 1) {
   1530             mAudioManager.setParameters(HEADSET_NREC + "=on");
   1531         } else {
   1532             mAudioManager.setParameters(HEADSET_NREC + "=off");
   1533         }
   1534     }
   1535 
   1536     private void processAtChld(int chld) {
   1537         if (mPhoneProxy != null) {
   1538             try {
   1539                 if (mPhoneProxy.processChld(chld)) {
   1540                     atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
   1541                 } else {
   1542                     atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
   1543                 }
   1544             } catch (RemoteException e) {
   1545                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
   1546                 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
   1547             }
   1548         } else {
   1549             Log.e(TAG, "Handsfree phone proxy null for At+Chld");
   1550             atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
   1551         }
   1552     }
   1553 
   1554     private void processSubscriberNumberRequest() {
   1555         if (mPhoneProxy != null) {
   1556             try {
   1557                 String number = mPhoneProxy.getSubscriberNumber();
   1558                 if (number != null) {
   1559                     atResponseStringNative("+CNUM: ,\"" + number + "\"," +
   1560                                            PhoneNumberUtils.toaFromString(number) + ",,4");
   1561                     atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
   1562                 }
   1563             } catch (RemoteException e) {
   1564                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
   1565                 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
   1566             }
   1567         } else {
   1568             Log.e(TAG, "Handsfree phone proxy null for At+CNUM");
   1569         }
   1570     }
   1571 
   1572     private void processAtCind() {
   1573         int call, call_setup;
   1574 
   1575         /* Handsfree carkits expect that +CIND is properly responded to
   1576          Hence we ensure that a proper response is sent
   1577          for the virtual call too.*/
   1578         if (isVirtualCallInProgress()) {
   1579             call = 1;
   1580             call_setup = 0;
   1581         } else {
   1582             // regular phone call
   1583             call = mPhoneState.getNumActiveCall();
   1584             call_setup = mPhoneState.getNumHeldCall();
   1585         }
   1586 
   1587         cindResponseNative(mPhoneState.getService(), call,
   1588                            call_setup, mPhoneState.getCallState(),
   1589                            mPhoneState.getSignal(), mPhoneState.getRoam(),
   1590                            mPhoneState.getBatteryCharge());
   1591     }
   1592 
   1593     private void processAtCops() {
   1594         if (mPhoneProxy != null) {
   1595             try {
   1596                 String operatorName = mPhoneProxy.getNetworkOperator();
   1597                 if (operatorName == null) {
   1598                     operatorName = "";
   1599                 }
   1600                 copsResponseNative(operatorName);
   1601             } catch (RemoteException e) {
   1602                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
   1603                 copsResponseNative("");
   1604             }
   1605         } else {
   1606             Log.e(TAG, "Handsfree phone proxy null for At+COPS");
   1607             copsResponseNative("");
   1608         }
   1609     }
   1610 
   1611     private void processAtClcc() {
   1612         if (mPhoneProxy != null) {
   1613             try {
   1614                 if(isVirtualCallInProgress()) {
   1615                     String phoneNumber = "";
   1616                     int type = PhoneNumberUtils.TOA_Unknown;
   1617                     try {
   1618                         phoneNumber = mPhoneProxy.getSubscriberNumber();
   1619                         type = PhoneNumberUtils.toaFromString(phoneNumber);
   1620                     } catch (RemoteException ee) {
   1621                         Log.e(TAG, "Unable to retrieve phone number"+
   1622                             "using IBluetoothHeadsetPhone proxy");
   1623                         phoneNumber = "";
   1624                     }
   1625                     clccResponseNative(1, 0, 0, 0, false, phoneNumber, type);
   1626                 }
   1627                 else if (!mPhoneProxy.listCurrentCalls()) {
   1628                     clccResponseNative(0, 0, 0, 0, false, "", 0);
   1629                 }
   1630             } catch (RemoteException e) {
   1631                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
   1632                 clccResponseNative(0, 0, 0, 0, false, "", 0);
   1633             }
   1634         } else {
   1635             Log.e(TAG, "Handsfree phone proxy null for At+CLCC");
   1636             clccResponseNative(0, 0, 0, 0, false, "", 0);
   1637         }
   1638     }
   1639 
   1640     private void processAtCscs(String atString, int type) {
   1641         log("processAtCscs - atString = "+ atString);
   1642         if(mPhonebook != null) {
   1643             mPhonebook.handleCscsCommand(atString, type);
   1644         }
   1645         else {
   1646             Log.e(TAG, "Phonebook handle null for At+CSCS");
   1647             atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
   1648         }
   1649     }
   1650 
   1651     private void processAtCpbs(String atString, int type) {
   1652         log("processAtCpbs - atString = "+ atString);
   1653         if(mPhonebook != null) {
   1654             mPhonebook.handleCpbsCommand(atString, type);
   1655         }
   1656         else {
   1657             Log.e(TAG, "Phonebook handle null for At+CPBS");
   1658             atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
   1659         }
   1660     }
   1661 
   1662     private void processAtCpbr(String atString, int type, BluetoothDevice mCurrentDevice) {
   1663         log("processAtCpbr - atString = "+ atString);
   1664         if(mPhonebook != null) {
   1665             mPhonebook.handleCpbrCommand(atString, type, mCurrentDevice);
   1666         }
   1667         else {
   1668             Log.e(TAG, "Phonebook handle null for At+CPBR");
   1669             atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
   1670         }
   1671     }
   1672 
   1673     /**
   1674      * Find a character ch, ignoring quoted sections.
   1675      * Return input.length() if not found.
   1676      */
   1677     static private int findChar(char ch, String input, int fromIndex) {
   1678         for (int i = fromIndex; i < input.length(); i++) {
   1679             char c = input.charAt(i);
   1680             if (c == '"') {
   1681                 i = input.indexOf('"', i + 1);
   1682                 if (i == -1) {
   1683                     return input.length();
   1684                 }
   1685             } else if (c == ch) {
   1686                 return i;
   1687             }
   1688         }
   1689         return input.length();
   1690     }
   1691 
   1692     /**
   1693      * Break an argument string into individual arguments (comma delimited).
   1694      * Integer arguments are turned into Integer objects. Otherwise a String
   1695      * object is used.
   1696      */
   1697     static private Object[] generateArgs(String input) {
   1698         int i = 0;
   1699         int j;
   1700         ArrayList<Object> out = new ArrayList<Object>();
   1701         while (i <= input.length()) {
   1702             j = findChar(',', input, i);
   1703 
   1704             String arg = input.substring(i, j);
   1705             try {
   1706                 out.add(new Integer(arg));
   1707             } catch (NumberFormatException e) {
   1708                 out.add(arg);
   1709             }
   1710 
   1711             i = j + 1; // move past comma
   1712         }
   1713         return out.toArray();
   1714     }
   1715 
   1716     private void processAtXevent(String atString) {
   1717         log("processAtXevent - atString = "+ atString);
   1718         if (atString.startsWith("=") && !atString.startsWith("=?")) {
   1719             Object[] args = generateArgs(atString.substring(1));
   1720             broadcastVendorSpecificEventIntent("+XEVENT",
   1721                                                BluetoothAssignedNumbers.PLANTRONICS,
   1722                                                BluetoothHeadset.AT_CMD_TYPE_SET,
   1723                                                args,
   1724                                                mCurrentDevice);
   1725             atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0);
   1726         }
   1727         else {
   1728             Log.e(TAG, "processAtXevent: command type error");
   1729             atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
   1730         }
   1731     }
   1732 
   1733     private void processUnknownAt(String atString) {
   1734         // TODO (BT)
   1735         log("processUnknownAt - atString = "+ atString);
   1736         String atCommand = parseUnknownAt(atString);
   1737         int commandType = getAtCommandType(atCommand);
   1738         if (atCommand.startsWith("+CSCS"))
   1739             processAtCscs(atCommand.substring(5), commandType);
   1740         else if (atCommand.startsWith("+CPBS"))
   1741             processAtCpbs(atCommand.substring(5), commandType);
   1742         else if (atCommand.startsWith("+CPBR"))
   1743             processAtCpbr(atCommand.substring(5), commandType, mCurrentDevice);
   1744         else if (atCommand.startsWith("+XEVENT"))
   1745             processAtXevent(atCommand.substring(7));
   1746         else
   1747             atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
   1748     }
   1749 
   1750     private void processKeyPressed() {
   1751         if (mPhoneState.getCallState() == HeadsetHalConstants.CALL_STATE_INCOMING) {
   1752             if (mPhoneProxy != null) {
   1753                 try {
   1754                     mPhoneProxy.answerCall();
   1755                 } catch (RemoteException e) {
   1756                     Log.e(TAG, Log.getStackTraceString(new Throwable()));
   1757                 }
   1758             } else {
   1759                 Log.e(TAG, "Handsfree phone proxy null for answering call");
   1760             }
   1761         } else if (mPhoneState.getNumActiveCall() > 0) {
   1762             if (!isAudioOn())
   1763             {
   1764                 connectAudioNative(getByteAddress(mCurrentDevice));
   1765             }
   1766             else
   1767             {
   1768                 if (mPhoneProxy != null) {
   1769                     try {
   1770                         mPhoneProxy.hangupCall();
   1771                     } catch (RemoteException e) {
   1772                         Log.e(TAG, Log.getStackTraceString(new Throwable()));
   1773                     }
   1774                 } else {
   1775                     Log.e(TAG, "Handsfree phone proxy null for hangup call");
   1776                 }
   1777             }
   1778         } else {
   1779             String dialNumber = mPhonebook.getLastDialledNumber();
   1780             if (dialNumber == null) {
   1781                 if (DBG) log("processKeyPressed, last dial number null");
   1782                 return;
   1783             }
   1784             Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
   1785                                        Uri.fromParts(SCHEME_TEL, dialNumber, null));
   1786             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   1787             mService.startActivity(intent);
   1788         }
   1789     }
   1790 
   1791     private void onConnectionStateChanged(int state, byte[] address) {
   1792         StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
   1793         event.valueInt = state;
   1794         event.device = getDevice(address);
   1795         sendMessage(STACK_EVENT, event);
   1796     }
   1797 
   1798     private void onAudioStateChanged(int state, byte[] address) {
   1799         StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED);
   1800         event.valueInt = state;
   1801         event.device = getDevice(address);
   1802         sendMessage(STACK_EVENT, event);
   1803     }
   1804 
   1805     private void onVrStateChanged(int state) {
   1806         StackEvent event = new StackEvent(EVENT_TYPE_VR_STATE_CHANGED);
   1807         event.valueInt = state;
   1808         sendMessage(STACK_EVENT, event);
   1809     }
   1810 
   1811     private void onAnswerCall() {
   1812         StackEvent event = new StackEvent(EVENT_TYPE_ANSWER_CALL);
   1813         sendMessage(STACK_EVENT, event);
   1814     }
   1815 
   1816     private void onHangupCall() {
   1817         StackEvent event = new StackEvent(EVENT_TYPE_HANGUP_CALL);
   1818         sendMessage(STACK_EVENT, event);
   1819     }
   1820 
   1821     private void onVolumeChanged(int type, int volume) {
   1822         StackEvent event = new StackEvent(EVENT_TYPE_VOLUME_CHANGED);
   1823         event.valueInt = type;
   1824         event.valueInt2 = volume;
   1825         sendMessage(STACK_EVENT, event);
   1826     }
   1827 
   1828     private void onDialCall(String number) {
   1829         StackEvent event = new StackEvent(EVENT_TYPE_DIAL_CALL);
   1830         event.valueString = number;
   1831         sendMessage(STACK_EVENT, event);
   1832     }
   1833 
   1834     private void onSendDtmf(int dtmf) {
   1835         StackEvent event = new StackEvent(EVENT_TYPE_SEND_DTMF);
   1836         event.valueInt = dtmf;
   1837         sendMessage(STACK_EVENT, event);
   1838     }
   1839 
   1840     private void onNoiceReductionEnable(boolean enable) {
   1841         StackEvent event = new StackEvent(EVENT_TYPE_NOICE_REDUCTION);
   1842         event.valueInt = enable ? 1 : 0;
   1843         sendMessage(STACK_EVENT, event);
   1844     }
   1845 
   1846     private void onAtChld(int chld) {
   1847         StackEvent event = new StackEvent(EVENT_TYPE_AT_CHLD);
   1848         event.valueInt = chld;
   1849         sendMessage(STACK_EVENT, event);
   1850     }
   1851 
   1852     private void onAtCnum() {
   1853         StackEvent event = new StackEvent(EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST);
   1854         sendMessage(STACK_EVENT, event);
   1855     }
   1856 
   1857     private void onAtCind() {
   1858         StackEvent event = new StackEvent(EVENT_TYPE_AT_CIND);
   1859         sendMessage(STACK_EVENT, event);
   1860     }
   1861 
   1862     private void onAtCops() {
   1863         StackEvent event = new StackEvent(EVENT_TYPE_AT_COPS);
   1864         sendMessage(STACK_EVENT, event);
   1865     }
   1866 
   1867     private void onAtClcc() {
   1868         StackEvent event = new StackEvent(EVENT_TYPE_AT_CLCC);
   1869         sendMessage(STACK_EVENT, event);
   1870     }
   1871 
   1872     private void onUnknownAt(String atString) {
   1873         StackEvent event = new StackEvent(EVENT_TYPE_UNKNOWN_AT);
   1874         event.valueString = atString;
   1875         sendMessage(STACK_EVENT, event);
   1876     }
   1877 
   1878     private void onKeyPressed() {
   1879         StackEvent event = new StackEvent(EVENT_TYPE_KEY_PRESSED);
   1880         sendMessage(STACK_EVENT, event);
   1881     }
   1882 
   1883     private void processIntentBatteryChanged(Intent intent) {
   1884         int batteryLevel = intent.getIntExtra("level", -1);
   1885         int scale = intent.getIntExtra("scale", -1);
   1886         if (batteryLevel == -1 || scale == -1 || scale == 0) {
   1887             Log.e(TAG, "Bad Battery Changed intent: " + batteryLevel + "," + scale);
   1888             return;
   1889         }
   1890         batteryLevel = batteryLevel * 5 / scale;
   1891         mPhoneState.setBatteryCharge(batteryLevel);
   1892     }
   1893 
   1894     private void processRoamChanged(boolean roam) {
   1895         mPhoneState.setRoam(roam ? HeadsetHalConstants.SERVICE_TYPE_ROAMING :
   1896                             HeadsetHalConstants.SERVICE_TYPE_HOME);
   1897     }
   1898 
   1899     private void processDeviceStateChanged(HeadsetDeviceState deviceState) {
   1900         notifyDeviceStatusNative(deviceState.mService, deviceState.mRoam, deviceState.mSignal,
   1901                                  deviceState.mBatteryCharge);
   1902     }
   1903 
   1904     private void processSendClccResponse(HeadsetClccResponse clcc) {
   1905         clccResponseNative(clcc.mIndex, clcc.mDirection, clcc.mStatus, clcc.mMode, clcc.mMpty,
   1906                            clcc.mNumber, clcc.mType);
   1907     }
   1908 
   1909     private String getCurrentDeviceName() {
   1910         String defaultName = "<unknown>";
   1911         if (mCurrentDevice == null) {
   1912             return defaultName;
   1913         }
   1914         String deviceName = mCurrentDevice.getName();
   1915         if (deviceName == null) {
   1916             return defaultName;
   1917         }
   1918         return deviceName;
   1919     }
   1920 
   1921     private byte[] getByteAddress(BluetoothDevice device) {
   1922         return Utils.getBytesFromAddress(device.getAddress());
   1923     }
   1924 
   1925     private BluetoothDevice getDevice(byte[] address) {
   1926         return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
   1927     }
   1928 
   1929     private boolean isInCall() {
   1930         return ((mPhoneState.getNumActiveCall() > 0) || (mPhoneState.getNumHeldCall() > 0) ||
   1931                 (mPhoneState.getCallState() != HeadsetHalConstants.CALL_STATE_IDLE));
   1932     }
   1933 
   1934     boolean isConnected() {
   1935         IState currentState = getCurrentState();
   1936         return (currentState == mConnected || currentState == mAudioOn);
   1937     }
   1938 
   1939     boolean okToConnect(BluetoothDevice device) {
   1940         AdapterService adapterService = AdapterService.getAdapterService();
   1941         int priority = mService.getPriority(device);
   1942         boolean ret = false;
   1943         //check if this is an incoming connection in Quiet mode.
   1944         if((adapterService == null) ||
   1945            ((adapterService.isQuietModeEnabled() == true) &&
   1946            (mTargetDevice == null))){
   1947             ret = false;
   1948         }
   1949         // check priority and accept or reject the connection. if priority is undefined
   1950         // it is likely that our SDP has not completed and peer is initiating the
   1951         // connection. Allow this connection, provided the device is bonded
   1952         else if((BluetoothProfile.PRIORITY_OFF < priority) ||
   1953                 ((BluetoothProfile.PRIORITY_UNDEFINED == priority) &&
   1954                 (device.getBondState() != BluetoothDevice.BOND_NONE))){
   1955             ret= true;
   1956         }
   1957         return ret;
   1958     }
   1959 
   1960     @Override
   1961     protected void log(String msg) {
   1962         if (DBG) {
   1963             super.log(msg);
   1964         }
   1965     }
   1966 
   1967     public void handleAccessPermissionResult(Intent intent) {
   1968         log("handleAccessPermissionResult");
   1969         if(mPhonebook != null) {
   1970             if (!mPhonebook.getCheckingAccessPermission()) {
   1971                 return;
   1972             }
   1973             int atCommandResult = 0;
   1974             int atCommandErrorCode = 0;
   1975             //HeadsetBase headset = mHandsfree.getHeadset();
   1976             // ASSERT: (headset != null) && headSet.isConnected()
   1977             // REASON: mCheckingAccessPermission is true, otherwise resetAtState
   1978             // has set mCheckingAccessPermission to false
   1979             if (intent.getAction().equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
   1980                 if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
   1981                     BluetoothDevice.CONNECTION_ACCESS_NO) ==
   1982                     BluetoothDevice.CONNECTION_ACCESS_YES) {
   1983                     if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
   1984                         mCurrentDevice.setTrust(true);
   1985                     }
   1986                     atCommandResult = mPhonebook.processCpbrCommand();
   1987                 }
   1988             }
   1989             mPhonebook.setCpbrIndex(-1);
   1990             mPhonebook.setCheckingAccessPermission(false);
   1991 
   1992             if (atCommandResult >= 0) {
   1993                 atResponseCodeNative(atCommandResult, atCommandErrorCode);
   1994             }
   1995             else
   1996                 log("handleAccessPermissionResult - RESULT_NONE");
   1997         }
   1998         else {
   1999             Log.e(TAG, "Phonebook handle null");
   2000             atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
   2001         }
   2002     }
   2003 
   2004     private static final String SCHEME_TEL = "tel";
   2005 
   2006     // Event types for STACK_EVENT message
   2007     final private static int EVENT_TYPE_NONE = 0;
   2008     final private static int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
   2009     final private static int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
   2010     final private static int EVENT_TYPE_VR_STATE_CHANGED = 3;
   2011     final private static int EVENT_TYPE_ANSWER_CALL = 4;
   2012     final private static int EVENT_TYPE_HANGUP_CALL = 5;
   2013     final private static int EVENT_TYPE_VOLUME_CHANGED = 6;
   2014     final private static int EVENT_TYPE_DIAL_CALL = 7;
   2015     final private static int EVENT_TYPE_SEND_DTMF = 8;
   2016     final private static int EVENT_TYPE_NOICE_REDUCTION = 9;
   2017     final private static int EVENT_TYPE_AT_CHLD = 10;
   2018     final private static int EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST = 11;
   2019     final private static int EVENT_TYPE_AT_CIND = 12;
   2020     final private static int EVENT_TYPE_AT_COPS = 13;
   2021     final private static int EVENT_TYPE_AT_CLCC = 14;
   2022     final private static int EVENT_TYPE_UNKNOWN_AT = 15;
   2023     final private static int EVENT_TYPE_KEY_PRESSED = 16;
   2024 
   2025     private class StackEvent {
   2026         int type = EVENT_TYPE_NONE;
   2027         int valueInt = 0;
   2028         int valueInt2 = 0;
   2029         String valueString = null;
   2030         BluetoothDevice device = null;
   2031 
   2032         private StackEvent(int type) {
   2033             this.type = type;
   2034         }
   2035     }
   2036 
   2037     /*package*/native boolean atResponseCodeNative(int responseCode, int errorCode);
   2038     /*package*/ native boolean atResponseStringNative(String responseString);
   2039 
   2040     private native static void classInitNative();
   2041     private native void initializeNative();
   2042     private native void cleanupNative();
   2043     private native boolean connectHfpNative(byte[] address);
   2044     private native boolean disconnectHfpNative(byte[] address);
   2045     private native boolean connectAudioNative(byte[] address);
   2046     private native boolean disconnectAudioNative(byte[] address);
   2047     private native boolean startVoiceRecognitionNative();
   2048     private native boolean stopVoiceRecognitionNative();
   2049     private native boolean setVolumeNative(int volumeType, int volume);
   2050     private native boolean cindResponseNative(int service, int numActive, int numHeld,
   2051                                               int callState, int signal, int roam,
   2052                                               int batteryCharge);
   2053     private native boolean notifyDeviceStatusNative(int networkState, int serviceType, int signal,
   2054                                                     int batteryCharge);
   2055 
   2056     private native boolean clccResponseNative(int index, int dir, int status, int mode,
   2057                                               boolean mpty, String number, int type);
   2058     private native boolean copsResponseNative(String operatorName);
   2059 
   2060     private native boolean phoneStateChangeNative(int numActive, int numHeld, int callState,
   2061                                                   String number, int type);
   2062 }
   2063