Home | History | Annotate | Download | only in phone
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.phone;
     18 
     19 import android.bluetooth.AtCommandHandler;
     20 import android.bluetooth.AtCommandResult;
     21 import android.bluetooth.AtParser;
     22 import android.bluetooth.BluetoothA2dp;
     23 import android.bluetooth.BluetoothAssignedNumbers;
     24 import android.bluetooth.BluetoothAdapter;
     25 import android.bluetooth.BluetoothDevice;
     26 import android.bluetooth.BluetoothHeadset;
     27 import android.bluetooth.BluetoothProfile;
     28 import android.bluetooth.BluetoothServerSocket;
     29 import android.bluetooth.BluetoothSocket;
     30 import android.bluetooth.HeadsetBase;
     31 import android.content.ActivityNotFoundException;
     32 import android.content.BroadcastReceiver;
     33 import android.content.Context;
     34 import android.content.Intent;
     35 import android.content.IntentFilter;
     36 import android.media.AudioManager;
     37 import android.net.Uri;
     38 import android.os.AsyncResult;
     39 import android.os.Bundle;
     40 import android.os.Handler;
     41 import android.os.HandlerThread;
     42 import android.os.Looper;
     43 import android.os.Message;
     44 import android.os.PowerManager;
     45 import android.os.PowerManager.WakeLock;
     46 import android.os.SystemProperties;
     47 import android.telephony.PhoneNumberUtils;
     48 import android.telephony.ServiceState;
     49 import android.telephony.SignalStrength;
     50 import android.util.Log;
     51 
     52 import com.android.internal.telephony.Call;
     53 import com.android.internal.telephony.Connection;
     54 import com.android.internal.telephony.Phone;
     55 import com.android.internal.telephony.TelephonyIntents;
     56 import com.android.internal.telephony.CallManager;
     57 
     58 import java.io.IOException;
     59 import java.io.InputStream;
     60 import java.util.LinkedList;
     61 
     62 /**
     63  * Bluetooth headset manager for the Phone app.
     64  * @hide
     65  */
     66 public class BluetoothHandsfree {
     67     private static final String TAG = "Bluetooth HS/HF";
     68     private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 1)
     69             && (SystemProperties.getInt("ro.debuggable", 0) == 1);
     70     private static final boolean VDBG = (PhoneApp.DBG_LEVEL >= 2);  // even more logging
     71 
     72     public static final int TYPE_UNKNOWN           = 0;
     73     public static final int TYPE_HEADSET           = 1;
     74     public static final int TYPE_HANDSFREE         = 2;
     75 
     76     /** The singleton instance. */
     77     private static BluetoothHandsfree sInstance;
     78 
     79     private final Context mContext;
     80     private final BluetoothAdapter mAdapter;
     81     private final CallManager mCM;
     82     private BluetoothA2dp mA2dp;
     83 
     84     private BluetoothDevice mA2dpDevice;
     85     private int mA2dpState;
     86     private boolean mPendingAudioState;
     87     private int mAudioState;
     88 
     89     private ServiceState mServiceState;
     90     private HeadsetBase mHeadset;
     91     private BluetoothHeadset mBluetoothHeadset;
     92     private int mHeadsetType;   // TYPE_UNKNOWN when not connected
     93     private boolean mAudioPossible;
     94     private BluetoothSocket mConnectedSco;
     95 
     96     private IncomingScoAcceptThread mIncomingScoThread = null;
     97     private ScoSocketConnectThread mConnectScoThread = null;
     98     private SignalScoCloseThread mSignalScoCloseThread = null;
     99 
    100     private AudioManager mAudioManager;
    101     private PowerManager mPowerManager;
    102 
    103     private boolean mPendingSco;  // waiting for a2dp sink to suspend before establishing SCO
    104     private boolean mA2dpSuspended;
    105     private boolean mUserWantsAudio;
    106     private WakeLock mStartCallWakeLock;  // held while waiting for the intent to start call
    107     private WakeLock mStartVoiceRecognitionWakeLock;  // held while waiting for voice recognition
    108 
    109     // AT command state
    110     private static final int GSM_MAX_CONNECTIONS = 6;  // Max connections allowed by GSM
    111     private static final int CDMA_MAX_CONNECTIONS = 2;  // Max connections allowed by CDMA
    112 
    113     private long mBgndEarliestConnectionTime = 0;
    114     private boolean mClip = false;  // Calling Line Information Presentation
    115     private boolean mIndicatorsEnabled = false;
    116     private boolean mCmee = false;  // Extended Error reporting
    117     private long[] mClccTimestamps; // Timestamps associated with each clcc index
    118     private boolean[] mClccUsed;     // Is this clcc index in use
    119     private boolean mWaitingForCallStart;
    120     private boolean mWaitingForVoiceRecognition;
    121     // do not connect audio until service connection is established
    122     // for 3-way supported devices, this is after AT+CHLD
    123     // for non-3-way supported devices, this is after AT+CMER (see spec)
    124     private boolean mServiceConnectionEstablished;
    125 
    126     private final BluetoothPhoneState mBluetoothPhoneState;  // for CIND and CIEV updates
    127     private final BluetoothAtPhonebook mPhonebook;
    128     private Phone.State mPhoneState = Phone.State.IDLE;
    129     CdmaPhoneCallState.PhoneCallState mCdmaThreeWayCallState =
    130                                             CdmaPhoneCallState.PhoneCallState.IDLE;
    131 
    132     private DebugThread mDebugThread;
    133     private int mScoGain = Integer.MIN_VALUE;
    134 
    135     private static Intent sVoiceCommandIntent;
    136 
    137     // Audio parameters
    138     private static final String HEADSET_NREC = "bt_headset_nrec";
    139     private static final String HEADSET_NAME = "bt_headset_name";
    140 
    141     private int mRemoteBrsf = 0;
    142     private int mLocalBrsf = 0;
    143 
    144     // CDMA specific flag used in context with BT devices having display capabilities
    145     // to show which Caller is active. This state might not be always true as in CDMA
    146     // networks if a caller drops off no update is provided to the Phone.
    147     // This flag is just used as a toggle to provide a update to the BT device to specify
    148     // which caller is active.
    149     private boolean mCdmaIsSecondCallActive = false;
    150     private boolean mCdmaCallsSwapped = false;
    151 
    152     /* Constants from Bluetooth Specification Hands-Free profile version 1.5 */
    153     private static final int BRSF_AG_THREE_WAY_CALLING = 1 << 0;
    154     private static final int BRSF_AG_EC_NR = 1 << 1;
    155     private static final int BRSF_AG_VOICE_RECOG = 1 << 2;
    156     private static final int BRSF_AG_IN_BAND_RING = 1 << 3;
    157     private static final int BRSF_AG_VOICE_TAG_NUMBE = 1 << 4;
    158     private static final int BRSF_AG_REJECT_CALL = 1 << 5;
    159     private static final int BRSF_AG_ENHANCED_CALL_STATUS = 1 <<  6;
    160     private static final int BRSF_AG_ENHANCED_CALL_CONTROL = 1 << 7;
    161     private static final int BRSF_AG_ENHANCED_ERR_RESULT_CODES = 1 << 8;
    162 
    163     private static final int BRSF_HF_EC_NR = 1 << 0;
    164     private static final int BRSF_HF_CW_THREE_WAY_CALLING = 1 << 1;
    165     private static final int BRSF_HF_CLIP = 1 << 2;
    166     private static final int BRSF_HF_VOICE_REG_ACT = 1 << 3;
    167     private static final int BRSF_HF_REMOTE_VOL_CONTROL = 1 << 4;
    168     private static final int BRSF_HF_ENHANCED_CALL_STATUS = 1 <<  5;
    169     private static final int BRSF_HF_ENHANCED_CALL_CONTROL = 1 << 6;
    170 
    171     // VirtualCall - true if Virtual Call is active, false otherwise
    172     private boolean mVirtualCallStarted = false;
    173 
    174     // Voice Recognition - true if Voice Recognition is active, false otherwise
    175     private boolean mVoiceRecognitionStarted;
    176 
    177     private HandsfreeMessageHandler mHandler;
    178 
    179     public static String typeToString(int type) {
    180         switch (type) {
    181         case TYPE_UNKNOWN:
    182             return "unknown";
    183         case TYPE_HEADSET:
    184             return "headset";
    185         case TYPE_HANDSFREE:
    186             return "handsfree";
    187         }
    188         return null;
    189     }
    190 
    191     /**
    192      * Initialize the singleton BluetoothHandsfree instance.
    193      * This is only done once, at startup, from PhoneApp.onCreate().
    194      */
    195     /* package */ static BluetoothHandsfree init(Context context, CallManager cm) {
    196         synchronized (BluetoothHandsfree.class) {
    197             if (sInstance == null) {
    198                 sInstance = new BluetoothHandsfree(context, cm);
    199             } else {
    200                 Log.wtf(TAG, "init() called multiple times!  sInstance = " + sInstance);
    201             }
    202             return sInstance;
    203         }
    204     }
    205 
    206     /** Private constructor; @see init() */
    207     private BluetoothHandsfree(Context context, CallManager cm) {
    208         mCM = cm;
    209         mContext = context;
    210         mAdapter = BluetoothAdapter.getDefaultAdapter();
    211         boolean bluetoothCapable = (mAdapter != null);
    212         mHeadset = null;
    213         mHeadsetType = TYPE_UNKNOWN; // nothing connected yet
    214         if (bluetoothCapable) {
    215             mAdapter.getProfileProxy(mContext, mProfileListener,
    216                                      BluetoothProfile.A2DP);
    217         }
    218         mA2dpState = BluetoothA2dp.STATE_DISCONNECTED;
    219         mA2dpDevice = null;
    220         mA2dpSuspended = false;
    221 
    222         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
    223         mStartCallWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
    224                                                        TAG + ":StartCall");
    225         mStartCallWakeLock.setReferenceCounted(false);
    226         mStartVoiceRecognitionWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
    227                                                        TAG + ":VoiceRecognition");
    228         mStartVoiceRecognitionWakeLock.setReferenceCounted(false);
    229 
    230         mLocalBrsf = BRSF_AG_THREE_WAY_CALLING |
    231                      BRSF_AG_EC_NR |
    232                      BRSF_AG_REJECT_CALL |
    233                      BRSF_AG_ENHANCED_CALL_STATUS;
    234 
    235         if (sVoiceCommandIntent == null) {
    236             sVoiceCommandIntent = new Intent(Intent.ACTION_VOICE_COMMAND);
    237             sVoiceCommandIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    238         }
    239         if (mContext.getPackageManager().resolveActivity(sVoiceCommandIntent, 0) != null &&
    240                 BluetoothHeadset.isBluetoothVoiceDialingEnabled(mContext)) {
    241             mLocalBrsf |= BRSF_AG_VOICE_RECOG;
    242         }
    243 
    244         HandlerThread thread = new HandlerThread("BluetoothHandsfreeHandler");
    245         thread.start();
    246         Looper looper = thread.getLooper();
    247         mHandler = new HandsfreeMessageHandler(looper);
    248         mBluetoothPhoneState = new BluetoothPhoneState();
    249         mUserWantsAudio = true;
    250         mVirtualCallStarted = false;
    251         mVoiceRecognitionStarted = false;
    252         mPhonebook = new BluetoothAtPhonebook(mContext, this);
    253         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    254         cdmaSetSecondCallState(false);
    255 
    256         if (bluetoothCapable) {
    257             resetAtState();
    258         }
    259 
    260     }
    261 
    262     /**
    263      * A thread that runs in the background waiting for a Sco Server Socket to
    264      * accept a connection. Even after a connection has been accepted, the Sco Server
    265      * continues to listen for new connections.
    266      */
    267     private class IncomingScoAcceptThread extends Thread{
    268         private final BluetoothServerSocket mIncomingServerSocket;
    269         private BluetoothSocket mIncomingSco;
    270         private boolean stopped = false;
    271 
    272         public IncomingScoAcceptThread() {
    273             BluetoothServerSocket serverSocket = null;
    274             try {
    275                 serverSocket = BluetoothAdapter.listenUsingScoOn();
    276             } catch (IOException e) {
    277                 Log.e(TAG, "Could not create BluetoothServerSocket");
    278                 stopped = true;
    279             }
    280             mIncomingServerSocket = serverSocket;
    281         }
    282 
    283         @Override
    284         public void run() {
    285             while (!stopped) {
    286                 try {
    287                     mIncomingSco = mIncomingServerSocket.accept();
    288                 } catch (IOException e) {
    289                     Log.e(TAG, "BluetoothServerSocket could not accept connection");
    290                 }
    291 
    292                 if (mIncomingSco != null) {
    293                     connectSco();
    294                 }
    295             }
    296         }
    297 
    298         private void connectSco() {
    299             synchronized (BluetoothHandsfree.this) {
    300                 if (!Thread.interrupted() && isHeadsetConnected() &&
    301                     (mAudioPossible || allowAudioAnytime()) &&
    302                     mConnectedSco == null) {
    303                     Log.i(TAG, "Routing audio for incoming SCO connection");
    304                     mConnectedSco = mIncomingSco;
    305                     mAudioManager.setBluetoothScoOn(true);
    306                     setAudioState(BluetoothHeadset.STATE_AUDIO_CONNECTED,
    307                         mHeadset.getRemoteDevice());
    308 
    309                     if (mSignalScoCloseThread == null) {
    310                         mSignalScoCloseThread = new SignalScoCloseThread();
    311                         mSignalScoCloseThread.setName("SignalScoCloseThread");
    312                         mSignalScoCloseThread.start();
    313                     }
    314                 } else {
    315                     Log.i(TAG, "Rejecting incoming SCO connection");
    316                     try {
    317                         mIncomingSco.close();
    318                     }catch (IOException e) {
    319                         Log.e(TAG, "Error when closing incoming Sco socket");
    320                     }
    321                     mIncomingSco = null;
    322                 }
    323             }
    324         }
    325 
    326         // must be called with BluetoothHandsfree locked
    327         void shutdown() {
    328             try {
    329                 mIncomingServerSocket.close();
    330             } catch (IOException e) {
    331                 Log.w(TAG, "Error when closing server socket");
    332             }
    333             stopped = true;
    334             interrupt();
    335         }
    336     }
    337 
    338     /**
    339      * A thread that runs in the background waiting for a Sco Socket to
    340      * connect.Once the socket is connected, this thread shall be
    341      * shutdown.
    342      */
    343     private class ScoSocketConnectThread extends Thread{
    344         private BluetoothSocket mOutgoingSco;
    345 
    346         public ScoSocketConnectThread(BluetoothDevice device) {
    347             try {
    348                 mOutgoingSco = device.createScoSocket();
    349             } catch (IOException e) {
    350                 Log.w(TAG, "Could not create BluetoothSocket");
    351                 failedScoConnect();
    352             }
    353         }
    354 
    355         @Override
    356         public void run() {
    357             try {
    358                 mOutgoingSco.connect();
    359             }catch (IOException connectException) {
    360                 Log.e(TAG, "BluetoothSocket could not connect");
    361                 mOutgoingSco = null;
    362                 failedScoConnect();
    363             }
    364 
    365             if (mOutgoingSco != null) {
    366                 connectSco();
    367             }
    368         }
    369 
    370         private void connectSco() {
    371             synchronized (BluetoothHandsfree.this) {
    372                 if (!Thread.interrupted() && isHeadsetConnected() && mConnectedSco == null) {
    373                     if (VDBG) log("Routing audio for outgoing SCO conection");
    374                     mConnectedSco = mOutgoingSco;
    375                     mAudioManager.setBluetoothScoOn(true);
    376 
    377                     setAudioState(BluetoothHeadset.STATE_AUDIO_CONNECTED,
    378                       mHeadset.getRemoteDevice());
    379 
    380                     if (mSignalScoCloseThread == null) {
    381                         mSignalScoCloseThread = new SignalScoCloseThread();
    382                         mSignalScoCloseThread.setName("SignalScoCloseThread");
    383                         mSignalScoCloseThread.start();
    384                     }
    385                 } else {
    386                     if (VDBG) log("Rejecting new connected outgoing SCO socket");
    387                     try {
    388                         mOutgoingSco.close();
    389                     }catch (IOException e) {
    390                         Log.e(TAG, "Error when closing Sco socket");
    391                     }
    392                     mOutgoingSco = null;
    393                     failedScoConnect();
    394                 }
    395             }
    396         }
    397 
    398         private void failedScoConnect() {
    399             // Wait for couple of secs before sending AUDIO_STATE_DISCONNECTED,
    400             // since an incoming SCO connection can happen immediately with
    401             // certain headsets.
    402             Message msg = Message.obtain(mHandler, SCO_AUDIO_STATE);
    403             msg.obj = mHeadset.getRemoteDevice();
    404             mHandler.sendMessageDelayed(msg, 2000);
    405 
    406             // Sync with interrupt() statement of shutdown method
    407             // This prevents resetting of a valid mConnectScoThread.
    408             // If this thread has been interrupted, it has been shutdown and
    409             // mConnectScoThread is/will be reset by the outer class.
    410             // We do not want to do it here since mConnectScoThread could be
    411             // assigned with a new object.
    412             synchronized (ScoSocketConnectThread.this) {
    413                 if (!isInterrupted()) {
    414                     resetConnectScoThread();
    415                 }
    416             }
    417         }
    418 
    419         // must be called with BluetoothHandsfree locked
    420         void shutdown() {
    421             closeConnectedSco();
    422 
    423             // sync with isInterrupted() check in failedScoConnect method
    424             // see explanation there
    425             synchronized (ScoSocketConnectThread.this) {
    426                 interrupt();
    427             }
    428         }
    429     }
    430 
    431     /*
    432      * Signals when a Sco connection has been closed
    433      */
    434     private class SignalScoCloseThread extends Thread{
    435         private boolean stopped = false;
    436 
    437         @Override
    438         public void run() {
    439             while (!stopped) {
    440                 BluetoothSocket connectedSco = null;
    441                 synchronized (BluetoothHandsfree.this) {
    442                     connectedSco = mConnectedSco;
    443                 }
    444                 if (connectedSco != null) {
    445                     byte b[] = new byte[1];
    446                     InputStream inStream = null;
    447                     try {
    448                         inStream = connectedSco.getInputStream();
    449                     } catch (IOException e) {}
    450 
    451                     if (inStream != null) {
    452                         try {
    453                             // inStream.read is a blocking call that won't ever
    454                             // return anything, but will throw an exception if the
    455                             // connection is closed
    456                             int ret = inStream.read(b, 0, 1);
    457                         }catch (IOException connectException) {
    458                             // call a message to close this thread and turn off audio
    459                             // we can't call audioOff directly because then
    460                             // the thread would try to close itself
    461                             Message msg = Message.obtain(mHandler, SCO_CLOSED);
    462                             mHandler.sendMessage(msg);
    463                             break;
    464                         }
    465                     }
    466                 }
    467             }
    468         }
    469 
    470         // must be called with BluetoothHandsfree locked
    471         void shutdown() {
    472             stopped = true;
    473             closeConnectedSco();
    474             interrupt();
    475         }
    476     }
    477 
    478     private void connectScoThread(){
    479         // Sync with setting mConnectScoThread to null to assure the validity of
    480         // the condition
    481         synchronized (ScoSocketConnectThread.class) {
    482             if (mConnectScoThread == null) {
    483                 BluetoothDevice device = mHeadset.getRemoteDevice();
    484                 if (getAudioState(device) == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
    485                     setAudioState(BluetoothHeadset.STATE_AUDIO_CONNECTING, device);
    486                 }
    487 
    488                 mConnectScoThread = new ScoSocketConnectThread(mHeadset.getRemoteDevice());
    489                 mConnectScoThread.setName("HandsfreeScoSocketConnectThread");
    490 
    491                 mConnectScoThread.start();
    492             }
    493         }
    494     }
    495 
    496     private void resetConnectScoThread() {
    497         // Sync with if (mConnectScoThread == null) check
    498         synchronized (ScoSocketConnectThread.class) {
    499             mConnectScoThread = null;
    500         }
    501     }
    502 
    503     // must be called with BluetoothHandsfree locked
    504     private void closeConnectedSco() {
    505         if (mConnectedSco != null) {
    506             try {
    507                 mConnectedSco.close();
    508             } catch (IOException e) {
    509                 Log.e(TAG, "Error when closing Sco socket");
    510             }
    511 
    512             BluetoothDevice device = null;
    513             if (mHeadset != null) {
    514                 device = mHeadset.getRemoteDevice();
    515             }
    516             mAudioManager.setBluetoothScoOn(false);
    517             setAudioState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, device);
    518 
    519             mConnectedSco = null;
    520         }
    521     }
    522 
    523     /* package */ synchronized void onBluetoothEnabled() {
    524         /* Bluez has a bug where it will always accept and then orphan
    525          * incoming SCO connections, regardless of whether we have a listening
    526          * SCO socket. So the best thing to do is always run a listening socket
    527          * while bluetooth is on so that at least we can disconnect it
    528          * immediately when we don't want it.
    529          */
    530 
    531         if (mIncomingScoThread == null) {
    532             mIncomingScoThread = new IncomingScoAcceptThread();
    533             mIncomingScoThread.setName("incomingScoAcceptThread");
    534             mIncomingScoThread.start();
    535         }
    536     }
    537 
    538     /* package */ synchronized void onBluetoothDisabled() {
    539         // Close off the SCO sockets
    540         audioOff();
    541 
    542         if (mIncomingScoThread != null) {
    543             mIncomingScoThread.shutdown();
    544             mIncomingScoThread = null;
    545         }
    546     }
    547 
    548     private boolean isHeadsetConnected() {
    549         if (mHeadset == null || mHeadsetType == TYPE_UNKNOWN) {
    550             return false;
    551         }
    552         return mHeadset.isConnected();
    553     }
    554 
    555     /* package */ synchronized void connectHeadset(HeadsetBase headset, int headsetType) {
    556         mHeadset = headset;
    557         mHeadsetType = headsetType;
    558         if (mHeadsetType == TYPE_HEADSET) {
    559             initializeHeadsetAtParser();
    560         } else {
    561             initializeHandsfreeAtParser();
    562         }
    563 
    564         // Headset vendor-specific commands
    565         registerAllVendorSpecificCommands();
    566 
    567         headset.startEventThread();
    568         configAudioParameters();
    569 
    570         if (inDebug()) {
    571             startDebug();
    572         }
    573 
    574         if (isIncallAudio()) {
    575             audioOn();
    576         } else if ( mCM.getFirstActiveRingingCall().isRinging()) {
    577             // need to update HS with RING when single ringing call exist
    578             mBluetoothPhoneState.ring();
    579         }
    580     }
    581 
    582     /* returns true if there is some kind of in-call audio we may wish to route
    583      * bluetooth to */
    584     private boolean isIncallAudio() {
    585         Call.State state = mCM.getActiveFgCallState();
    586 
    587         return (state == Call.State.ACTIVE || state == Call.State.ALERTING);
    588     }
    589 
    590     /* package */ synchronized void disconnectHeadset() {
    591         audioOff();
    592 
    593         // No need to check if isVirtualCallInProgress()
    594         // terminateScoUsingVirtualVoiceCall() does the check
    595         terminateScoUsingVirtualVoiceCall();
    596 
    597         mHeadsetType = TYPE_UNKNOWN;
    598         stopDebug();
    599         resetAtState();
    600     }
    601 
    602     /* package */ synchronized void resetAtState() {
    603         mClip = false;
    604         mIndicatorsEnabled = false;
    605         mServiceConnectionEstablished = false;
    606         mCmee = false;
    607         mClccTimestamps = new long[GSM_MAX_CONNECTIONS];
    608         mClccUsed = new boolean[GSM_MAX_CONNECTIONS];
    609         for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
    610             mClccUsed[i] = false;
    611         }
    612         mRemoteBrsf = 0;
    613         mPhonebook.resetAtState();
    614     }
    615 
    616     /* package */ HeadsetBase getHeadset() {
    617         return mHeadset;
    618     }
    619 
    620     private void configAudioParameters() {
    621         String name = mHeadset.getRemoteDevice().getName();
    622         if (name == null) {
    623             name = "<unknown>";
    624         }
    625         mAudioManager.setParameters(HEADSET_NAME+"="+name+";"+HEADSET_NREC+"=on");
    626     }
    627 
    628 
    629     /** Represents the data that we send in a +CIND or +CIEV command to the HF
    630      */
    631     private class BluetoothPhoneState {
    632         // 0: no service
    633         // 1: service
    634         private int mService;
    635 
    636         // 0: no active call
    637         // 1: active call (where active means audio is routed - not held call)
    638         private int mCall;
    639 
    640         // 0: not in call setup
    641         // 1: incoming call setup
    642         // 2: outgoing call setup
    643         // 3: remote party being alerted in an outgoing call setup
    644         private int mCallsetup;
    645 
    646         // 0: no calls held
    647         // 1: held call and active call
    648         // 2: held call only
    649         private int mCallheld;
    650 
    651         // cellular signal strength of AG: 0-5
    652         private int mSignal;
    653 
    654         // cellular signal strength in CSQ rssi scale
    655         private int mRssi;  // for CSQ
    656 
    657         // 0: roaming not active (home)
    658         // 1: roaming active
    659         private int mRoam;
    660 
    661         // battery charge of AG: 0-5
    662         private int mBattchg;
    663 
    664         // 0: not registered
    665         // 1: registered, home network
    666         // 5: registered, roaming
    667         private int mStat;  // for CREG
    668 
    669         private String mRingingNumber;  // Context for in-progress RING's
    670         private int    mRingingType;
    671         private boolean mIgnoreRing = false;
    672         private boolean mStopRing = false;
    673 
    674         // current or last call start timestamp
    675         private long mCallStartTime = 0;
    676         // time window to reconnect remotely-disconnected SCO
    677         // in mili-seconds
    678         private static final int RETRY_SCO_TIME_WINDOW = 1000;
    679 
    680         private static final int SERVICE_STATE_CHANGED = 1;
    681         private static final int PRECISE_CALL_STATE_CHANGED = 2;
    682         private static final int RING = 3;
    683         private static final int PHONE_CDMA_CALL_WAITING = 4;
    684 
    685         private Handler mStateChangeHandler = new Handler() {
    686             @Override
    687             public void handleMessage(Message msg) {
    688                 switch(msg.what) {
    689                 case RING:
    690                     AtCommandResult result = ring();
    691                     if (result != null) {
    692                         sendURC(result.toString());
    693                     }
    694                     break;
    695                 case SERVICE_STATE_CHANGED:
    696                     ServiceState state = (ServiceState) ((AsyncResult) msg.obj).result;
    697                     updateServiceState(sendUpdate(), state);
    698                     break;
    699                 case PRECISE_CALL_STATE_CHANGED:
    700                 case PHONE_CDMA_CALL_WAITING:
    701                     Connection connection = null;
    702                     if (((AsyncResult) msg.obj).result instanceof Connection) {
    703                         connection = (Connection) ((AsyncResult) msg.obj).result;
    704                     }
    705                     handlePreciseCallStateChange(sendUpdate(), connection);
    706                     break;
    707                 }
    708             }
    709         };
    710 
    711         private BluetoothPhoneState() {
    712             // init members
    713             // TODO May consider to repalce the default phone's state and signal
    714             //      by CallManagter's state and signal
    715             updateServiceState(false, mCM.getDefaultPhone().getServiceState());
    716             handlePreciseCallStateChange(false, null);
    717             mBattchg = 5;  // There is currently no API to get battery level
    718                            // on demand, so set to 5 and wait for an update
    719             mSignal = asuToSignal(mCM.getDefaultPhone().getSignalStrength());
    720 
    721             // register for updates
    722             // Use the service state of default phone as BT service state to
    723             // avoid situation such as no cell or wifi connection but still
    724             // reporting in service (since SipPhone always reports in service).
    725             mCM.getDefaultPhone().registerForServiceStateChanged(mStateChangeHandler,
    726                                                   SERVICE_STATE_CHANGED, null);
    727             mCM.registerForPreciseCallStateChanged(mStateChangeHandler,
    728                     PRECISE_CALL_STATE_CHANGED, null);
    729             mCM.registerForCallWaiting(mStateChangeHandler,
    730                 PHONE_CDMA_CALL_WAITING, null);
    731 
    732             IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
    733             filter.addAction(TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED);
    734             filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
    735             filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
    736             mContext.registerReceiver(mStateReceiver, filter);
    737         }
    738 
    739         private void updateBtPhoneStateAfterRadioTechnologyChange() {
    740             if(VDBG) Log.d(TAG, "updateBtPhoneStateAfterRadioTechnologyChange...");
    741 
    742             //Unregister all events from the old obsolete phone
    743             mCM.getDefaultPhone().unregisterForServiceStateChanged(mStateChangeHandler);
    744             mCM.unregisterForPreciseCallStateChanged(mStateChangeHandler);
    745             mCM.unregisterForCallWaiting(mStateChangeHandler);
    746 
    747             //Register all events new to the new active phone
    748             mCM.getDefaultPhone().registerForServiceStateChanged(mStateChangeHandler,
    749                                                   SERVICE_STATE_CHANGED, null);
    750             mCM.registerForPreciseCallStateChanged(mStateChangeHandler,
    751                     PRECISE_CALL_STATE_CHANGED, null);
    752             mCM.registerForCallWaiting(mStateChangeHandler,
    753                 PHONE_CDMA_CALL_WAITING, null);
    754         }
    755 
    756         private boolean sendUpdate() {
    757             return isHeadsetConnected() && mHeadsetType == TYPE_HANDSFREE && mIndicatorsEnabled
    758                    && mServiceConnectionEstablished;
    759         }
    760 
    761         private boolean sendClipUpdate() {
    762             return isHeadsetConnected() && mHeadsetType == TYPE_HANDSFREE && mClip &&
    763                    mServiceConnectionEstablished;
    764         }
    765 
    766         private boolean sendRingUpdate() {
    767             if (isHeadsetConnected() && !mIgnoreRing && !mStopRing &&
    768                     mCM.getFirstActiveRingingCall().isRinging()) {
    769                 if (mHeadsetType == TYPE_HANDSFREE) {
    770                     return mServiceConnectionEstablished ? true : false;
    771                 }
    772                 return true;
    773             }
    774             return false;
    775         }
    776 
    777         private void stopRing() {
    778             mStopRing = true;
    779         }
    780 
    781         /* convert [0,31] ASU signal strength to the [0,5] expected by
    782          * bluetooth devices. Scale is similar to status bar policy
    783          */
    784         private int gsmAsuToSignal(SignalStrength signalStrength) {
    785             int asu = signalStrength.getGsmSignalStrength();
    786             if      (asu >= 16) return 5;
    787             else if (asu >= 8)  return 4;
    788             else if (asu >= 4)  return 3;
    789             else if (asu >= 2)  return 2;
    790             else if (asu >= 1)  return 1;
    791             else                return 0;
    792         }
    793 
    794         /**
    795          * Convert the cdma / evdo db levels to appropriate icon level.
    796          * The scale is similar to the one used in status bar policy.
    797          *
    798          * @param signalStrength
    799          * @return the icon level
    800          */
    801         private int cdmaDbmEcioToSignal(SignalStrength signalStrength) {
    802             int levelDbm = 0;
    803             int levelEcio = 0;
    804             int cdmaIconLevel = 0;
    805             int evdoIconLevel = 0;
    806             int cdmaDbm = signalStrength.getCdmaDbm();
    807             int cdmaEcio = signalStrength.getCdmaEcio();
    808 
    809             if (cdmaDbm >= -75) levelDbm = 4;
    810             else if (cdmaDbm >= -85) levelDbm = 3;
    811             else if (cdmaDbm >= -95) levelDbm = 2;
    812             else if (cdmaDbm >= -100) levelDbm = 1;
    813             else levelDbm = 0;
    814 
    815             // Ec/Io are in dB*10
    816             if (cdmaEcio >= -90) levelEcio = 4;
    817             else if (cdmaEcio >= -110) levelEcio = 3;
    818             else if (cdmaEcio >= -130) levelEcio = 2;
    819             else if (cdmaEcio >= -150) levelEcio = 1;
    820             else levelEcio = 0;
    821 
    822             cdmaIconLevel = (levelDbm < levelEcio) ? levelDbm : levelEcio;
    823 
    824             if (mServiceState != null &&
    825                   (mServiceState.getRadioTechnology() == ServiceState.RADIO_TECHNOLOGY_EVDO_0 ||
    826                    mServiceState.getRadioTechnology() == ServiceState.RADIO_TECHNOLOGY_EVDO_A)) {
    827                   int evdoEcio = signalStrength.getEvdoEcio();
    828                   int evdoSnr = signalStrength.getEvdoSnr();
    829                   int levelEvdoEcio = 0;
    830                   int levelEvdoSnr = 0;
    831 
    832                   // Ec/Io are in dB*10
    833                   if (evdoEcio >= -650) levelEvdoEcio = 4;
    834                   else if (evdoEcio >= -750) levelEvdoEcio = 3;
    835                   else if (evdoEcio >= -900) levelEvdoEcio = 2;
    836                   else if (evdoEcio >= -1050) levelEvdoEcio = 1;
    837                   else levelEvdoEcio = 0;
    838 
    839                   if (evdoSnr > 7) levelEvdoSnr = 4;
    840                   else if (evdoSnr > 5) levelEvdoSnr = 3;
    841                   else if (evdoSnr > 3) levelEvdoSnr = 2;
    842                   else if (evdoSnr > 1) levelEvdoSnr = 1;
    843                   else levelEvdoSnr = 0;
    844 
    845                   evdoIconLevel = (levelEvdoEcio < levelEvdoSnr) ? levelEvdoEcio : levelEvdoSnr;
    846             }
    847             // TODO(): There is a bug open regarding what should be sent.
    848             return (cdmaIconLevel > evdoIconLevel) ?  cdmaIconLevel : evdoIconLevel;
    849 
    850         }
    851 
    852 
    853         private int asuToSignal(SignalStrength signalStrength) {
    854             if (signalStrength.isGsm()) {
    855                 return gsmAsuToSignal(signalStrength);
    856             } else {
    857                 return cdmaDbmEcioToSignal(signalStrength);
    858             }
    859         }
    860 
    861 
    862         /* convert [0,5] signal strength to a rssi signal strength for CSQ
    863          * which is [0,31]. Despite the same scale, this is not the same value
    864          * as ASU.
    865          */
    866         private int signalToRssi(int signal) {
    867             // using C4A suggested values
    868             switch (signal) {
    869             case 0: return 0;
    870             case 1: return 4;
    871             case 2: return 8;
    872             case 3: return 13;
    873             case 4: return 19;
    874             case 5: return 31;
    875             }
    876             return 0;
    877         }
    878 
    879 
    880         private final BroadcastReceiver mStateReceiver = new BroadcastReceiver() {
    881             @Override
    882             public void onReceive(Context context, Intent intent) {
    883                 if (intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) {
    884                     Message msg = mHandler.obtainMessage(BATTERY_CHANGED, intent);
    885                     mHandler.sendMessage(msg);
    886                 } else if (intent.getAction().equals(
    887                             TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED)) {
    888                     Message msg = mHandler.obtainMessage(SIGNAL_STRENGTH_CHANGED,
    889                                                                     intent);
    890                     mHandler.sendMessage(msg);
    891                 } else if (intent.getAction().equals(
    892                     BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
    893                     int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
    894                         BluetoothProfile.STATE_DISCONNECTED);
    895                     int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
    896                         BluetoothProfile.STATE_DISCONNECTED);
    897                     BluetoothDevice device =
    898                             intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    899 
    900 
    901                     // We are only concerned about Connected sinks to suspend and resume
    902                     // them. We can safely ignore SINK_STATE_CHANGE for other devices.
    903                     if (device == null || (mA2dpDevice != null && !device.equals(mA2dpDevice))) {
    904                         return;
    905                     }
    906 
    907                     synchronized (BluetoothHandsfree.this) {
    908                         mA2dpState = state;
    909                         if (state == BluetoothProfile.STATE_DISCONNECTED) {
    910                             mA2dpDevice = null;
    911                         } else {
    912                             mA2dpDevice = device;
    913                         }
    914                         if (oldState == BluetoothA2dp.STATE_PLAYING &&
    915                             mA2dpState == BluetoothProfile.STATE_CONNECTED) {
    916                             if (mA2dpSuspended) {
    917                                 if (mPendingSco) {
    918                                     mHandler.removeMessages(MESSAGE_CHECK_PENDING_SCO);
    919                                     if (DBG) log("A2DP suspended, completing SCO");
    920                                     connectScoThread();
    921                                     mPendingSco = false;
    922                                 }
    923                             }
    924                         }
    925                     }
    926                 } else if (intent.getAction().
    927                            equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
    928                     mPhonebook.handleAccessPermissionResult(intent);
    929                 }
    930             }
    931         };
    932 
    933         private synchronized void updateBatteryState(Intent intent) {
    934             int batteryLevel = intent.getIntExtra("level", -1);
    935             int scale = intent.getIntExtra("scale", -1);
    936             if (batteryLevel == -1 || scale == -1) {
    937                 return;  // ignore
    938             }
    939             batteryLevel = batteryLevel * 5 / scale;
    940             if (mBattchg != batteryLevel) {
    941                 mBattchg = batteryLevel;
    942                 if (sendUpdate()) {
    943                     sendURC("+CIEV: 7," + mBattchg);
    944                 }
    945             }
    946         }
    947 
    948         private synchronized void updateSignalState(Intent intent) {
    949             // NOTE this function is called by the BroadcastReceiver mStateReceiver after intent
    950             // ACTION_SIGNAL_STRENGTH_CHANGED and by the DebugThread mDebugThread
    951             if (!isHeadsetConnected()) {
    952                 return;
    953             }
    954 
    955             SignalStrength signalStrength = SignalStrength.newFromBundle(intent.getExtras());
    956             int signal;
    957 
    958             if (signalStrength != null) {
    959                 signal = asuToSignal(signalStrength);
    960                 mRssi = signalToRssi(signal);  // no unsolicited CSQ
    961                 if (signal != mSignal) {
    962                     mSignal = signal;
    963                     if (sendUpdate()) {
    964                         sendURC("+CIEV: 5," + mSignal);
    965                     }
    966                 }
    967             } else {
    968                 Log.e(TAG, "Signal Strength null");
    969             }
    970         }
    971 
    972         private synchronized void updateServiceState(boolean sendUpdate, ServiceState state) {
    973             int service = state.getState() == ServiceState.STATE_IN_SERVICE ? 1 : 0;
    974             int roam = state.getRoaming() ? 1 : 0;
    975             int stat;
    976             AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
    977             mServiceState = state;
    978             if (service == 0) {
    979                 stat = 0;
    980             } else {
    981                 stat = (roam == 1) ? 5 : 1;
    982             }
    983 
    984             if (service != mService) {
    985                 mService = service;
    986                 if (sendUpdate) {
    987                     result.addResponse("+CIEV: 1," + mService);
    988                 }
    989             }
    990             if (roam != mRoam) {
    991                 mRoam = roam;
    992                 if (sendUpdate) {
    993                     result.addResponse("+CIEV: 6," + mRoam);
    994                 }
    995             }
    996             if (stat != mStat) {
    997                 mStat = stat;
    998                 if (sendUpdate) {
    999                     result.addResponse(toCregString());
   1000                 }
   1001             }
   1002 
   1003             sendURC(result.toString());
   1004         }
   1005 
   1006         private synchronized void handlePreciseCallStateChange(boolean sendUpdate,
   1007                 Connection connection) {
   1008             int call = 0;
   1009             int callsetup = 0;
   1010             int callheld = 0;
   1011             int prevCallsetup = mCallsetup;
   1012             AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
   1013             Call foregroundCall = mCM.getActiveFgCall();
   1014             Call backgroundCall = mCM.getFirstActiveBgCall();
   1015             Call ringingCall = mCM.getFirstActiveRingingCall();
   1016 
   1017             if (VDBG) log("updatePhoneState()");
   1018 
   1019             // This function will get called when the Precise Call State
   1020             // {@link Call.State} changes. Hence, we might get this update
   1021             // even if the {@link Phone.state} is same as before.
   1022             // Check for the same.
   1023 
   1024             Phone.State newState = mCM.getState();
   1025             if (newState != mPhoneState) {
   1026                 mPhoneState = newState;
   1027                 switch (mPhoneState) {
   1028                 case IDLE:
   1029                     mUserWantsAudio = true;  // out of call - reset state
   1030                     audioOff();
   1031                     break;
   1032                 default:
   1033                     callStarted();
   1034                 }
   1035             }
   1036 
   1037             switch(foregroundCall.getState()) {
   1038             case ACTIVE:
   1039                 call = 1;
   1040                 mAudioPossible = true;
   1041                 break;
   1042             case DIALING:
   1043                 callsetup = 2;
   1044                 mAudioPossible = true;
   1045                 // We also need to send a Call started indication
   1046                 // for cases where the 2nd MO was initiated was
   1047                 // from a *BT hands free* and is waiting for a
   1048                 // +BLND: OK response
   1049                 // There is a special case handling of the same case
   1050                 // for CDMA below
   1051                 if (mCM.getFgPhone().getPhoneType() == Phone.PHONE_TYPE_GSM) {
   1052                     callStarted();
   1053                 }
   1054                 break;
   1055             case ALERTING:
   1056                 callsetup = 3;
   1057                 // Open the SCO channel for the outgoing call.
   1058                 mCallStartTime = System.currentTimeMillis();
   1059                 audioOn();
   1060                 mAudioPossible = true;
   1061                 break;
   1062             case DISCONNECTING:
   1063                 // This is a transient state, we don't want to send
   1064                 // any AT commands during this state.
   1065                 call = mCall;
   1066                 callsetup = mCallsetup;
   1067                 callheld = mCallheld;
   1068                 break;
   1069             default:
   1070                 mAudioPossible = false;
   1071             }
   1072 
   1073             switch(ringingCall.getState()) {
   1074             case INCOMING:
   1075             case WAITING:
   1076                 callsetup = 1;
   1077                 break;
   1078             case DISCONNECTING:
   1079                 // This is a transient state, we don't want to send
   1080                 // any AT commands during this state.
   1081                 call = mCall;
   1082                 callsetup = mCallsetup;
   1083                 callheld = mCallheld;
   1084                 break;
   1085             }
   1086 
   1087             switch(backgroundCall.getState()) {
   1088             case HOLDING:
   1089                 if (call == 1) {
   1090                     callheld = 1;
   1091                 } else {
   1092                     call = 1;
   1093                     callheld = 2;
   1094                 }
   1095                 break;
   1096             case DISCONNECTING:
   1097                 // This is a transient state, we don't want to send
   1098                 // any AT commands during this state.
   1099                 call = mCall;
   1100                 callsetup = mCallsetup;
   1101                 callheld = mCallheld;
   1102                 break;
   1103             }
   1104 
   1105             if (mCall != call) {
   1106                 if (call == 1) {
   1107                     // This means that a call has transitioned from NOT ACTIVE to ACTIVE.
   1108                     // Switch on audio.
   1109                     mCallStartTime = System.currentTimeMillis();
   1110                     audioOn();
   1111                 }
   1112                 mCall = call;
   1113                 if (sendUpdate) {
   1114                     result.addResponse("+CIEV: 2," + mCall);
   1115                 }
   1116             }
   1117             if (mCallsetup != callsetup) {
   1118                 mCallsetup = callsetup;
   1119                 if (sendUpdate) {
   1120                     // If mCall = 0, send CIEV
   1121                     // mCall = 1, mCallsetup = 0, send CIEV
   1122                     // mCall = 1, mCallsetup = 1, send CIEV after CCWA,
   1123                     // if 3 way supported.
   1124                     // mCall = 1, mCallsetup = 2 / 3 -> send CIEV,
   1125                     // if 3 way is supported
   1126                     if (mCall != 1 || mCallsetup == 0 ||
   1127                         mCallsetup != 1 && (mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) {
   1128                         result.addResponse("+CIEV: 3," + mCallsetup);
   1129                     }
   1130                 }
   1131             }
   1132 
   1133             if (mCM.getDefaultPhone().getPhoneType() == Phone.PHONE_TYPE_CDMA) {
   1134                 PhoneApp app = PhoneApp.getInstance();
   1135                 if (app.cdmaPhoneCallState != null) {
   1136                     CdmaPhoneCallState.PhoneCallState currCdmaThreeWayCallState =
   1137                             app.cdmaPhoneCallState.getCurrentCallState();
   1138                     CdmaPhoneCallState.PhoneCallState prevCdmaThreeWayCallState =
   1139                         app.cdmaPhoneCallState.getPreviousCallState();
   1140 
   1141                     log("CDMA call state: " + currCdmaThreeWayCallState + " prev state:" +
   1142                         prevCdmaThreeWayCallState);
   1143                     callheld = getCdmaCallHeldStatus(currCdmaThreeWayCallState,
   1144                                                      prevCdmaThreeWayCallState);
   1145 
   1146                     if (mCdmaThreeWayCallState != currCdmaThreeWayCallState) {
   1147                         // In CDMA, the network does not provide any feedback
   1148                         // to the phone when the 2nd MO call goes through the
   1149                         // stages of DIALING > ALERTING -> ACTIVE we fake the
   1150                         // sequence
   1151                         if ((currCdmaThreeWayCallState ==
   1152                                 CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
   1153                                     && app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing()) {
   1154                             mAudioPossible = true;
   1155                             if (sendUpdate) {
   1156                                 if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) {
   1157                                     result.addResponse("+CIEV: 3,2");
   1158                                     // Mimic putting the call on hold
   1159                                     result.addResponse("+CIEV: 4,1");
   1160                                     mCallheld = callheld;
   1161                                     result.addResponse("+CIEV: 3,3");
   1162                                     result.addResponse("+CIEV: 3,0");
   1163                                 }
   1164                             }
   1165                             // We also need to send a Call started indication
   1166                             // for cases where the 2nd MO was initiated was
   1167                             // from a *BT hands free* and is waiting for a
   1168                             // +BLND: OK response
   1169                             callStarted();
   1170                         }
   1171 
   1172                         // In CDMA, the network does not provide any feedback to
   1173                         // the phone when a user merges a 3way call or swaps
   1174                         // between two calls we need to send a CIEV response
   1175                         // indicating that a call state got changed which should
   1176                         // trigger a CLCC update request from the BT client.
   1177                         if (currCdmaThreeWayCallState ==
   1178                                 CdmaPhoneCallState.PhoneCallState.CONF_CALL &&
   1179                                 prevCdmaThreeWayCallState ==
   1180                                   CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
   1181                             mAudioPossible = true;
   1182                             if (sendUpdate) {
   1183                                 if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) {
   1184                                     result.addResponse("+CIEV: 2,1");
   1185                                     result.addResponse("+CIEV: 3,0");
   1186                                 }
   1187                             }
   1188                         }
   1189                     }
   1190                     mCdmaThreeWayCallState = currCdmaThreeWayCallState;
   1191                 }
   1192             }
   1193 
   1194             boolean callsSwitched;
   1195 
   1196             if (mCM.getDefaultPhone().getPhoneType() == Phone.PHONE_TYPE_CDMA &&
   1197                 mCdmaThreeWayCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
   1198                 callsSwitched = mCdmaCallsSwapped;
   1199             } else {
   1200                 callsSwitched =
   1201                     (callheld == 1 && ! (backgroundCall.getEarliestConnectTime() ==
   1202                         mBgndEarliestConnectionTime));
   1203                 mBgndEarliestConnectionTime = backgroundCall.getEarliestConnectTime();
   1204             }
   1205 
   1206 
   1207             if (mCallheld != callheld || callsSwitched) {
   1208                 mCallheld = callheld;
   1209                 if (sendUpdate) {
   1210                     result.addResponse("+CIEV: 4," + mCallheld);
   1211                 }
   1212             }
   1213 
   1214             if (callsetup == 1 && callsetup != prevCallsetup) {
   1215                 // new incoming call
   1216                 String number = null;
   1217                 int type = 128;
   1218                 // find incoming phone number and type
   1219                 if (connection == null) {
   1220                     connection = ringingCall.getEarliestConnection();
   1221                     if (connection == null) {
   1222                         Log.e(TAG, "Could not get a handle on Connection object for new " +
   1223                               "incoming call");
   1224                     }
   1225                 }
   1226                 if (connection != null) {
   1227                     number = connection.getAddress();
   1228                     if (number != null) {
   1229                         type = PhoneNumberUtils.toaFromString(number);
   1230                     }
   1231                 }
   1232                 if (number == null) {
   1233                     number = "";
   1234                 }
   1235                 if ((call != 0 || callheld != 0) && sendUpdate) {
   1236                     // call waiting
   1237                     if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) {
   1238                         result.addResponse("+CCWA: \"" + number + "\"," + type);
   1239                         result.addResponse("+CIEV: 3," + callsetup);
   1240                     }
   1241                 } else {
   1242                     // regular new incoming call
   1243                     mRingingNumber = number;
   1244                     mRingingType = type;
   1245                     mIgnoreRing = false;
   1246                     mStopRing = false;
   1247 
   1248                     if ((mLocalBrsf & BRSF_AG_IN_BAND_RING) != 0x0) {
   1249                         mCallStartTime = System.currentTimeMillis();
   1250                         audioOn();
   1251                     }
   1252                     result.addResult(ring());
   1253                 }
   1254             }
   1255             sendURC(result.toString());
   1256         }
   1257 
   1258         private int getCdmaCallHeldStatus(CdmaPhoneCallState.PhoneCallState currState,
   1259                                   CdmaPhoneCallState.PhoneCallState prevState) {
   1260             int callheld;
   1261             // Update the Call held information
   1262             if (currState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
   1263                 if (prevState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
   1264                     callheld = 0; //0: no calls held, as now *both* the caller are active
   1265                 } else {
   1266                     callheld = 1; //1: held call and active call, as on answering a
   1267                             // Call Waiting, one of the caller *is* put on hold
   1268                 }
   1269             } else if (currState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
   1270                 callheld = 1; //1: held call and active call, as on make a 3 Way Call
   1271                         // the first caller *is* put on hold
   1272             } else {
   1273                 callheld = 0; //0: no calls held as this is a SINGLE_ACTIVE call
   1274             }
   1275             return callheld;
   1276         }
   1277 
   1278 
   1279         private AtCommandResult ring() {
   1280             if (sendRingUpdate()) {
   1281                 AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
   1282                 result.addResponse("RING");
   1283                 if (sendClipUpdate()) {
   1284                     result.addResponse("+CLIP: \"" + mRingingNumber + "\"," + mRingingType);
   1285                 }
   1286 
   1287                 Message msg = mStateChangeHandler.obtainMessage(RING);
   1288                 mStateChangeHandler.sendMessageDelayed(msg, 3000);
   1289                 return result;
   1290             }
   1291             return null;
   1292         }
   1293 
   1294         private synchronized String toCregString() {
   1295             return new String("+CREG: 1," + mStat);
   1296         }
   1297 
   1298         private synchronized void updateCallHeld() {
   1299             if (mCallheld != 0) {
   1300                 mCallheld = 0;
   1301                 sendURC("+CIEV: 4,0");
   1302             }
   1303         }
   1304 
   1305         private synchronized AtCommandResult toCindResult() {
   1306             AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
   1307             int call, call_setup;
   1308 
   1309             // Handsfree carkits expect that +CIND is properly responded to.
   1310             // Hence we ensure that a proper response is sent for the virtual call too.
   1311             if (isVirtualCallInProgress()) {
   1312                 call = 1;
   1313                 call_setup = 0;
   1314             } else {
   1315                 // regular phone call
   1316                 call = mCall;
   1317                 call_setup = mCallsetup;
   1318             }
   1319 
   1320             mSignal = asuToSignal(mCM.getDefaultPhone().getSignalStrength());
   1321             String status = "+CIND: " + mService + "," + call + "," + call_setup + "," +
   1322                             mCallheld + "," + mSignal + "," + mRoam + "," + mBattchg;
   1323             result.addResponse(status);
   1324             return result;
   1325         }
   1326 
   1327         private synchronized AtCommandResult toCsqResult() {
   1328             AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
   1329             String status = "+CSQ: " + mRssi + ",99";
   1330             result.addResponse(status);
   1331             return result;
   1332         }
   1333 
   1334 
   1335         private synchronized AtCommandResult getCindTestResult() {
   1336             return new AtCommandResult("+CIND: (\"service\",(0-1))," + "(\"call\",(0-1))," +
   1337                         "(\"callsetup\",(0-3)),(\"callheld\",(0-2)),(\"signal\",(0-5))," +
   1338                         "(\"roam\",(0-1)),(\"battchg\",(0-5))");
   1339         }
   1340 
   1341         private synchronized void ignoreRing() {
   1342             mCallsetup = 0;
   1343             mIgnoreRing = true;
   1344             if (sendUpdate()) {
   1345                 sendURC("+CIEV: 3," + mCallsetup);
   1346             }
   1347         }
   1348 
   1349         private void scoClosed() {
   1350             // sync on mUserWantsAudio change
   1351             synchronized(BluetoothHandsfree.this) {
   1352                 if (mUserWantsAudio &&
   1353                     System.currentTimeMillis() - mCallStartTime < RETRY_SCO_TIME_WINDOW) {
   1354                     Message msg = mHandler.obtainMessage(SCO_CONNECTION_CHECK);
   1355                     mHandler.sendMessage(msg);
   1356                 }
   1357             }
   1358         }
   1359     };
   1360 
   1361     private static final int SCO_CLOSED = 3;
   1362     private static final int CHECK_CALL_STARTED = 4;
   1363     private static final int CHECK_VOICE_RECOGNITION_STARTED = 5;
   1364     private static final int MESSAGE_CHECK_PENDING_SCO = 6;
   1365     private static final int SCO_AUDIO_STATE = 7;
   1366     private static final int SCO_CONNECTION_CHECK = 8;
   1367     private static final int BATTERY_CHANGED = 9;
   1368     private static final int SIGNAL_STRENGTH_CHANGED = 10;
   1369 
   1370     private final class HandsfreeMessageHandler extends Handler {
   1371         private HandsfreeMessageHandler(Looper looper) {
   1372             super(looper);
   1373         }
   1374 
   1375         @Override
   1376         public void handleMessage(Message msg) {
   1377             switch (msg.what) {
   1378             case SCO_CLOSED:
   1379                 synchronized (BluetoothHandsfree.this) {
   1380                     // synchronized
   1381                     // Make atomic against audioOn, userWantsAudioOn
   1382                     // TODO finer lock to decouple from other call flow such as
   1383                     //      mWaitingForCallStart change
   1384 
   1385                     audioOff();
   1386                     // notify mBluetoothPhoneState that the SCO channel has closed
   1387                     mBluetoothPhoneState.scoClosed();
   1388                 }
   1389                 break;
   1390             case CHECK_CALL_STARTED:
   1391                 synchronized (BluetoothHandsfree.this) {
   1392                     // synchronized
   1393                     // Protect test/change of mWaitingForCallStart
   1394                     if (mWaitingForCallStart) {
   1395                         mWaitingForCallStart = false;
   1396                         Log.e(TAG, "Timeout waiting for call to start");
   1397                         sendURC("ERROR");
   1398                         if (mStartCallWakeLock.isHeld()) {
   1399                             mStartCallWakeLock.release();
   1400                         }
   1401                     }
   1402                 }
   1403                 break;
   1404             case CHECK_VOICE_RECOGNITION_STARTED:
   1405                 synchronized (BluetoothHandsfree.this) {
   1406                     // synchronized
   1407                     // Protect test/change of mWaitingForVoiceRecognition
   1408                     if (mWaitingForVoiceRecognition) {
   1409                         mWaitingForVoiceRecognition = false;
   1410                         Log.e(TAG, "Timeout waiting for voice recognition to start");
   1411                         sendURC("ERROR");
   1412                     }
   1413                 }
   1414                 break;
   1415             case MESSAGE_CHECK_PENDING_SCO:
   1416                 synchronized (BluetoothHandsfree.this) {
   1417                     // synchronized
   1418                     // Protect test/change of mPendingSco
   1419                     if (mPendingSco && isA2dpMultiProfile()) {
   1420                         Log.w(TAG, "Timeout suspending A2DP for SCO (mA2dpState = " +
   1421                                 mA2dpState + "). Starting SCO anyway");
   1422                         connectScoThread();
   1423                         mPendingSco = false;
   1424                     }
   1425                 }
   1426                 break;
   1427             case SCO_AUDIO_STATE:
   1428                 BluetoothDevice device = (BluetoothDevice) msg.obj;
   1429                 if (getAudioState(device) == BluetoothHeadset.STATE_AUDIO_CONNECTING) {
   1430                     setAudioState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, device);
   1431                 }
   1432                 break;
   1433             case SCO_CONNECTION_CHECK:
   1434                 synchronized (mBluetoothPhoneState) {
   1435                     // synchronized on mCall change
   1436                     if (mBluetoothPhoneState.mCall == 1) {
   1437                         // Sometimes, the SCO channel is torn down by HF with no reason.
   1438                         // Because we are still in active call, reconnect SCO.
   1439                         // audioOn does nothing if the SCO is already on.
   1440                         audioOn();
   1441                     }
   1442                 }
   1443                 break;
   1444             case BATTERY_CHANGED:
   1445                 mBluetoothPhoneState.updateBatteryState((Intent) msg.obj);
   1446                 break;
   1447             case SIGNAL_STRENGTH_CHANGED:
   1448                 mBluetoothPhoneState.updateSignalState((Intent) msg.obj);
   1449                 break;
   1450             }
   1451         }
   1452     }
   1453 
   1454     private synchronized void setAudioState(int state, BluetoothDevice device) {
   1455         if (VDBG) log("setAudioState(" + state + ")");
   1456         if (mBluetoothHeadset == null) {
   1457             mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEADSET);
   1458             mPendingAudioState = true;
   1459             mAudioState = state;
   1460             return;
   1461         }
   1462         mBluetoothHeadset.setAudioState(device, state);
   1463     }
   1464 
   1465     private synchronized int getAudioState(BluetoothDevice device) {
   1466         if (mBluetoothHeadset == null) return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
   1467         return mBluetoothHeadset.getAudioState(device);
   1468     }
   1469 
   1470     private BluetoothProfile.ServiceListener mProfileListener =
   1471             new BluetoothProfile.ServiceListener() {
   1472         public void onServiceConnected(int profile, BluetoothProfile proxy) {
   1473             if (profile == BluetoothProfile.HEADSET) {
   1474                 mBluetoothHeadset = (BluetoothHeadset) proxy;
   1475                 synchronized(BluetoothHandsfree.this) {
   1476                     if (mPendingAudioState) {
   1477                         mBluetoothHeadset.setAudioState(mHeadset.getRemoteDevice(), mAudioState);
   1478                         mPendingAudioState = false;
   1479                     }
   1480                 }
   1481             } else if (profile == BluetoothProfile.A2DP) {
   1482                 mA2dp = (BluetoothA2dp) proxy;
   1483             }
   1484         }
   1485         public void onServiceDisconnected(int profile) {
   1486             if (profile == BluetoothProfile.HEADSET) {
   1487                 mBluetoothHeadset = null;
   1488             } else if (profile == BluetoothProfile.A2DP) {
   1489                 mA2dp = null;
   1490             }
   1491         }
   1492     };
   1493 
   1494     /*
   1495      * Put the AT command, company ID, arguments, and device in an Intent and broadcast it.
   1496      */
   1497     private void broadcastVendorSpecificEventIntent(String command,
   1498                                                     int companyId,
   1499                                                     int commandType,
   1500                                                     Object[] arguments,
   1501                                                     BluetoothDevice device) {
   1502         if (VDBG) log("broadcastVendorSpecificEventIntent(" + command + ")");
   1503         Intent intent =
   1504                 new Intent(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT);
   1505         intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD, command);
   1506         intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE,
   1507                         commandType);
   1508         // assert: all elements of args are Serializable
   1509         intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS, arguments);
   1510         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
   1511 
   1512         intent.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY
   1513             + "." + Integer.toString(companyId));
   1514 
   1515         mContext.sendBroadcast(intent, android.Manifest.permission.BLUETOOTH);
   1516     }
   1517 
   1518     void updateBtHandsfreeAfterRadioTechnologyChange() {
   1519         if (VDBG) Log.d(TAG, "updateBtHandsfreeAfterRadioTechnologyChange...");
   1520 
   1521         mBluetoothPhoneState.updateBtPhoneStateAfterRadioTechnologyChange();
   1522     }
   1523 
   1524     /** Request to establish SCO (audio) connection to bluetooth
   1525      * headset/handsfree, if one is connected. Does not block.
   1526      * Returns false if the user has requested audio off, or if there
   1527      * is some other immediate problem that will prevent BT audio.
   1528      */
   1529     /* package */ synchronized boolean audioOn() {
   1530         if (VDBG) log("audioOn()");
   1531         if (!isHeadsetConnected()) {
   1532             if (DBG) log("audioOn(): headset is not connected!");
   1533             return false;
   1534         }
   1535         if (mHeadsetType == TYPE_HANDSFREE && !mServiceConnectionEstablished) {
   1536             if (DBG) log("audioOn(): service connection not yet established!");
   1537             return false;
   1538         }
   1539 
   1540         if (mConnectedSco != null) {
   1541             if (DBG) log("audioOn(): audio is already connected");
   1542             return true;
   1543         }
   1544 
   1545         if (!mUserWantsAudio) {
   1546             if (DBG) log("audioOn(): user requested no audio, ignoring");
   1547             return false;
   1548         }
   1549 
   1550         if (mPendingSco) {
   1551             if (DBG) log("audioOn(): SCO already pending");
   1552             return true;
   1553         }
   1554 
   1555         mA2dpSuspended = false;
   1556         mPendingSco = false;
   1557         if (isA2dpMultiProfile() && mA2dpState == BluetoothA2dp.STATE_PLAYING) {
   1558             if (DBG) log("suspending A2DP stream for SCO");
   1559             mA2dpSuspended = mA2dp.suspendSink(mA2dpDevice);
   1560             if (mA2dpSuspended) {
   1561                 mPendingSco = true;
   1562                 Message msg = mHandler.obtainMessage(MESSAGE_CHECK_PENDING_SCO);
   1563                 mHandler.sendMessageDelayed(msg, 2000);
   1564             } else {
   1565                 Log.w(TAG, "Could not suspend A2DP stream for SCO, going ahead with SCO");
   1566             }
   1567         }
   1568 
   1569         if (!mPendingSco) {
   1570             connectScoThread();
   1571         }
   1572 
   1573         return true;
   1574     }
   1575 
   1576     /** Used to indicate the user requested BT audio on.
   1577      *  This will establish SCO (BT audio), even if the user requested it off
   1578      *  previously on this call.
   1579      */
   1580     /* package */ synchronized void userWantsAudioOn() {
   1581         mUserWantsAudio = true;
   1582         audioOn();
   1583     }
   1584     /** Used to indicate the user requested BT audio off.
   1585      *  This will prevent us from establishing BT audio again during this call
   1586      *  if audioOn() is called.
   1587      */
   1588     /* package */ synchronized void userWantsAudioOff() {
   1589         mUserWantsAudio = false;
   1590         audioOff();
   1591     }
   1592 
   1593     /** Request to disconnect SCO (audio) connection to bluetooth
   1594      * headset/handsfree, if one is connected. Does not block.
   1595      */
   1596     /* package */ synchronized void audioOff() {
   1597         if (VDBG) log("audioOff(): mPendingSco: " + mPendingSco +
   1598                 ", mConnectedSco: " + mConnectedSco +
   1599                 ", mA2dpState: " + mA2dpState +
   1600                 ", mA2dpSuspended: " + mA2dpSuspended);
   1601 
   1602         if (mA2dpSuspended) {
   1603             if (isA2dpMultiProfile()) {
   1604                 if (DBG) log("resuming A2DP stream after disconnecting SCO");
   1605                 mA2dp.resumeSink(mA2dpDevice);
   1606             }
   1607             mA2dpSuspended = false;
   1608         }
   1609 
   1610         mPendingSco = false;
   1611 
   1612         if (mSignalScoCloseThread != null) {
   1613             mSignalScoCloseThread.shutdown();
   1614             mSignalScoCloseThread = null;
   1615         }
   1616 
   1617         // Sync with setting mConnectScoThread to null to assure the validity of
   1618         // the condition
   1619         synchronized (ScoSocketConnectThread.class) {
   1620             if (mConnectScoThread != null) {
   1621                 mConnectScoThread.shutdown();
   1622                 resetConnectScoThread();
   1623             }
   1624         }
   1625 
   1626         closeConnectedSco();    // Should be closed already, but just in case
   1627     }
   1628 
   1629     /* package */ boolean isAudioOn() {
   1630         return (mConnectedSco != null);
   1631     }
   1632 
   1633     private boolean isA2dpMultiProfile() {
   1634         return mA2dp != null && mHeadset != null && mA2dpDevice != null &&
   1635                 mA2dpDevice.equals(mHeadset.getRemoteDevice());
   1636     }
   1637 
   1638     /* package */ void ignoreRing() {
   1639         mBluetoothPhoneState.ignoreRing();
   1640     }
   1641 
   1642     private void sendURC(String urc) {
   1643         if (isHeadsetConnected()) {
   1644             mHeadset.sendURC(urc);
   1645         }
   1646     }
   1647 
   1648     /** helper to redial last dialled number */
   1649     private AtCommandResult redial() {
   1650         String number = mPhonebook.getLastDialledNumber();
   1651         if (number == null) {
   1652             // spec seems to suggest sending ERROR if we dont have a
   1653             // number to redial
   1654             if (VDBG) log("Bluetooth redial requested (+BLDN), but no previous " +
   1655                   "outgoing calls found. Ignoring");
   1656             return new AtCommandResult(AtCommandResult.ERROR);
   1657         }
   1658         // Outgoing call initiated by the handsfree device
   1659         // Send terminateScoUsingVirtualVoiceCall
   1660         terminateScoUsingVirtualVoiceCall();
   1661         Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
   1662                 Uri.fromParts(Constants.SCHEME_TEL, number, null));
   1663         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   1664         mContext.startActivity(intent);
   1665 
   1666         // We do not immediately respond OK, wait until we get a phone state
   1667         // update. If we return OK now and the handsfree immeidately requests
   1668         // our phone state it will say we are not in call yet which confuses
   1669         // some devices
   1670         expectCallStart();
   1671         return new AtCommandResult(AtCommandResult.UNSOLICITED);  // send nothing
   1672     }
   1673 
   1674     /** Build the +CLCC result
   1675      *  The complexity arises from the fact that we need to maintain the same
   1676      *  CLCC index even as a call moves between states. */
   1677     private synchronized AtCommandResult gsmGetClccResult() {
   1678         // Collect all known connections
   1679         Connection[] clccConnections = new Connection[GSM_MAX_CONNECTIONS];  // indexed by CLCC index
   1680         LinkedList<Connection> newConnections = new LinkedList<Connection>();
   1681         LinkedList<Connection> connections = new LinkedList<Connection>();
   1682 
   1683         Call foregroundCall = mCM.getActiveFgCall();
   1684         Call backgroundCall = mCM.getFirstActiveBgCall();
   1685         Call ringingCall = mCM.getFirstActiveRingingCall();
   1686 
   1687         if (ringingCall.getState().isAlive()) {
   1688             connections.addAll(ringingCall.getConnections());
   1689         }
   1690         if (foregroundCall.getState().isAlive()) {
   1691             connections.addAll(foregroundCall.getConnections());
   1692         }
   1693         if (backgroundCall.getState().isAlive()) {
   1694             connections.addAll(backgroundCall.getConnections());
   1695         }
   1696 
   1697         // Mark connections that we already known about
   1698         boolean clccUsed[] = new boolean[GSM_MAX_CONNECTIONS];
   1699         for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
   1700             clccUsed[i] = mClccUsed[i];
   1701             mClccUsed[i] = false;
   1702         }
   1703         for (Connection c : connections) {
   1704             boolean found = false;
   1705             long timestamp = c.getCreateTime();
   1706             for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
   1707                 if (clccUsed[i] && timestamp == mClccTimestamps[i]) {
   1708                     mClccUsed[i] = true;
   1709                     found = true;
   1710                     clccConnections[i] = c;
   1711                     break;
   1712                 }
   1713             }
   1714             if (!found) {
   1715                 newConnections.add(c);
   1716             }
   1717         }
   1718 
   1719         // Find a CLCC index for new connections
   1720         while (!newConnections.isEmpty()) {
   1721             // Find lowest empty index
   1722             int i = 0;
   1723             while (mClccUsed[i]) i++;
   1724             // Find earliest connection
   1725             long earliestTimestamp = newConnections.get(0).getCreateTime();
   1726             Connection earliestConnection = newConnections.get(0);
   1727             for (int j = 0; j < newConnections.size(); j++) {
   1728                 long timestamp = newConnections.get(j).getCreateTime();
   1729                 if (timestamp < earliestTimestamp) {
   1730                     earliestTimestamp = timestamp;
   1731                     earliestConnection = newConnections.get(j);
   1732                 }
   1733             }
   1734 
   1735             // update
   1736             mClccUsed[i] = true;
   1737             mClccTimestamps[i] = earliestTimestamp;
   1738             clccConnections[i] = earliestConnection;
   1739             newConnections.remove(earliestConnection);
   1740         }
   1741 
   1742         // Build CLCC
   1743         AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
   1744         for (int i = 0; i < clccConnections.length; i++) {
   1745             if (mClccUsed[i]) {
   1746                 String clccEntry = connectionToClccEntry(i, clccConnections[i]);
   1747                 if (clccEntry != null) {
   1748                     result.addResponse(clccEntry);
   1749                 }
   1750             }
   1751         }
   1752 
   1753         return result;
   1754     }
   1755 
   1756     /** Convert a Connection object into a single +CLCC result */
   1757     private String connectionToClccEntry(int index, Connection c) {
   1758         int state;
   1759         switch (c.getState()) {
   1760         case ACTIVE:
   1761             state = 0;
   1762             break;
   1763         case HOLDING:
   1764             state = 1;
   1765             break;
   1766         case DIALING:
   1767             state = 2;
   1768             break;
   1769         case ALERTING:
   1770             state = 3;
   1771             break;
   1772         case INCOMING:
   1773             state = 4;
   1774             break;
   1775         case WAITING:
   1776             state = 5;
   1777             break;
   1778         default:
   1779             return null;  // bad state
   1780         }
   1781 
   1782         int mpty = 0;
   1783         Call call = c.getCall();
   1784         if (call != null) {
   1785             mpty = call.isMultiparty() ? 1 : 0;
   1786         }
   1787 
   1788         int direction = c.isIncoming() ? 1 : 0;
   1789 
   1790         String number = c.getAddress();
   1791         int type = -1;
   1792         if (number != null) {
   1793             type = PhoneNumberUtils.toaFromString(number);
   1794         }
   1795 
   1796         String result = "+CLCC: " + (index + 1) + "," + direction + "," + state + ",0," + mpty;
   1797         if (number != null) {
   1798             result += ",\"" + number + "\"," + type;
   1799         }
   1800         return result;
   1801     }
   1802 
   1803     /** Build the +CLCC result for CDMA
   1804      *  The complexity arises from the fact that we need to maintain the same
   1805      *  CLCC index even as a call moves between states. */
   1806     private synchronized AtCommandResult cdmaGetClccResult() {
   1807         // In CDMA at one time a user can have only two live/active connections
   1808         Connection[] clccConnections = new Connection[CDMA_MAX_CONNECTIONS];// indexed by CLCC index
   1809         Call foregroundCall = mCM.getActiveFgCall();
   1810         Call ringingCall = mCM.getFirstActiveRingingCall();
   1811 
   1812         Call.State ringingCallState = ringingCall.getState();
   1813         // If the Ringing Call state is INCOMING, that means this is the very first call
   1814         // hence there should not be any Foreground Call
   1815         if (ringingCallState == Call.State.INCOMING) {
   1816             if (VDBG) log("Filling clccConnections[0] for INCOMING state");
   1817             clccConnections[0] = ringingCall.getLatestConnection();
   1818         } else if (foregroundCall.getState().isAlive()) {
   1819             // Getting Foreground Call connection based on Call state
   1820             if (ringingCall.isRinging()) {
   1821                 if (VDBG) log("Filling clccConnections[0] & [1] for CALL WAITING state");
   1822                 clccConnections[0] = foregroundCall.getEarliestConnection();
   1823                 clccConnections[1] = ringingCall.getLatestConnection();
   1824             } else {
   1825                 if (foregroundCall.getConnections().size() <= 1) {
   1826                     // Single call scenario
   1827                     if (VDBG) log("Filling clccConnections[0] with ForgroundCall latest connection");
   1828                     clccConnections[0] = foregroundCall.getLatestConnection();
   1829                 } else {
   1830                     // Multiple Call scenario. This would be true for both
   1831                     // CONF_CALL and THRWAY_ACTIVE state
   1832                     if (VDBG) log("Filling clccConnections[0] & [1] with ForgroundCall connections");
   1833                     clccConnections[0] = foregroundCall.getEarliestConnection();
   1834                     clccConnections[1] = foregroundCall.getLatestConnection();
   1835                 }
   1836             }
   1837         }
   1838 
   1839         // Update the mCdmaIsSecondCallActive flag based on the Phone call state
   1840         if (PhoneApp.getInstance().cdmaPhoneCallState.getCurrentCallState()
   1841                 == CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE) {
   1842             cdmaSetSecondCallState(false);
   1843         } else if (PhoneApp.getInstance().cdmaPhoneCallState.getCurrentCallState()
   1844                 == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
   1845             cdmaSetSecondCallState(true);
   1846         }
   1847 
   1848         // Build CLCC
   1849         AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
   1850         for (int i = 0; (i < clccConnections.length) && (clccConnections[i] != null); i++) {
   1851             String clccEntry = cdmaConnectionToClccEntry(i, clccConnections[i]);
   1852             if (clccEntry != null) {
   1853                 result.addResponse(clccEntry);
   1854             }
   1855         }
   1856 
   1857         return result;
   1858     }
   1859 
   1860     /** Convert a Connection object into a single +CLCC result for CDMA phones */
   1861     private String cdmaConnectionToClccEntry(int index, Connection c) {
   1862         int state;
   1863         PhoneApp app = PhoneApp.getInstance();
   1864         CdmaPhoneCallState.PhoneCallState currCdmaCallState =
   1865                 app.cdmaPhoneCallState.getCurrentCallState();
   1866         CdmaPhoneCallState.PhoneCallState prevCdmaCallState =
   1867                 app.cdmaPhoneCallState.getPreviousCallState();
   1868 
   1869         if ((prevCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
   1870                 && (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL)) {
   1871             // If the current state is reached after merging two calls
   1872             // we set the state of all the connections as ACTIVE
   1873             state = 0;
   1874         } else {
   1875             switch (c.getState()) {
   1876             case ACTIVE:
   1877                 // For CDMA since both the connections are set as active by FW after accepting
   1878                 // a Call waiting or making a 3 way call, we need to set the state specifically
   1879                 // to ACTIVE/HOLDING based on the mCdmaIsSecondCallActive flag. This way the
   1880                 // CLCC result will allow BT devices to enable the swap or merge options
   1881                 if (index == 0) { // For the 1st active connection
   1882                     state = mCdmaIsSecondCallActive ? 1 : 0;
   1883                 } else { // for the 2nd active connection
   1884                     state = mCdmaIsSecondCallActive ? 0 : 1;
   1885                 }
   1886                 break;
   1887             case HOLDING:
   1888                 state = 1;
   1889                 break;
   1890             case DIALING:
   1891                 state = 2;
   1892                 break;
   1893             case ALERTING:
   1894                 state = 3;
   1895                 break;
   1896             case INCOMING:
   1897                 state = 4;
   1898                 break;
   1899             case WAITING:
   1900                 state = 5;
   1901                 break;
   1902             default:
   1903                 return null;  // bad state
   1904             }
   1905         }
   1906 
   1907         int mpty = 0;
   1908         if (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
   1909             if (prevCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
   1910                 // If the current state is reached after merging two calls
   1911                 // we set the multiparty call true.
   1912                 mpty = 1;
   1913             } else {
   1914                 // CALL_CONF state is not from merging two calls, but from
   1915                 // accepting the second call. In this case first will be on
   1916                 // hold in most cases but in some cases its already merged.
   1917                 // However, we will follow the common case and the test case
   1918                 // as per Bluetooth SIG PTS
   1919                 mpty = 0;
   1920             }
   1921         } else {
   1922             mpty = 0;
   1923         }
   1924 
   1925         int direction = c.isIncoming() ? 1 : 0;
   1926 
   1927         String number = c.getAddress();
   1928         int type = -1;
   1929         if (number != null) {
   1930             type = PhoneNumberUtils.toaFromString(number);
   1931         }
   1932 
   1933         String result = "+CLCC: " + (index + 1) + "," + direction + "," + state + ",0," + mpty;
   1934         if (number != null) {
   1935             result += ",\"" + number + "\"," + type;
   1936         }
   1937         return result;
   1938     }
   1939 
   1940     /*
   1941      * Register a vendor-specific command.
   1942      * @param commandName the name of the command.  For example, if the expected
   1943      * incoming command is <code>AT+FOO=bar,baz</code>, the value of this should be
   1944      * <code>"+FOO"</code>.
   1945      * @param companyId the Bluetooth SIG Company Identifier
   1946      * @param parser the AtParser on which to register the command
   1947      */
   1948     private void registerVendorSpecificCommand(String commandName,
   1949                                                int companyId,
   1950                                                AtParser parser) {
   1951         parser.register(commandName,
   1952                         new VendorSpecificCommandHandler(commandName, companyId));
   1953     }
   1954 
   1955     /*
   1956      * Register all vendor-specific commands here.
   1957      */
   1958     private void registerAllVendorSpecificCommands() {
   1959         AtParser parser = mHeadset.getAtParser();
   1960 
   1961         // Plantronics-specific headset events go here
   1962         registerVendorSpecificCommand("+XEVENT",
   1963                                       BluetoothAssignedNumbers.PLANTRONICS,
   1964                                       parser);
   1965     }
   1966 
   1967     /**
   1968      * Register AT Command handlers to implement the Headset profile
   1969      */
   1970     private void initializeHeadsetAtParser() {
   1971         if (VDBG) log("Registering Headset AT commands");
   1972         AtParser parser = mHeadset.getAtParser();
   1973         // Headsets usually only have one button, which is meant to cause the
   1974         // HS to send us AT+CKPD=200 or AT+CKPD.
   1975         parser.register("+CKPD", new AtCommandHandler() {
   1976             private AtCommandResult headsetButtonPress() {
   1977                 if (mCM.getFirstActiveRingingCall().isRinging()) {
   1978                     // Answer the call
   1979                     mBluetoothPhoneState.stopRing();
   1980                     sendURC("OK");
   1981                     PhoneUtils.answerCall(mCM.getFirstActiveRingingCall());
   1982                     // If in-band ring tone is supported, SCO connection will already
   1983                     // be up and the following call will just return.
   1984                     audioOn();
   1985                     return new AtCommandResult(AtCommandResult.UNSOLICITED);
   1986                 } else if (mCM.hasActiveFgCall()) {
   1987                     if (!isAudioOn()) {
   1988                         // Transfer audio from AG to HS
   1989                         audioOn();
   1990                     } else {
   1991                         if (mHeadset.getDirection() == HeadsetBase.DIRECTION_INCOMING &&
   1992                           (System.currentTimeMillis() - mHeadset.getConnectTimestamp()) < 5000) {
   1993                             // Headset made a recent ACL connection to us - and
   1994                             // made a mandatory AT+CKPD request to connect
   1995                             // audio which races with our automatic audio
   1996                             // setup.  ignore
   1997                         } else {
   1998                             // Hang up the call
   1999                             audioOff();
   2000                             PhoneUtils.hangup(PhoneApp.getInstance().mCM);
   2001                         }
   2002                     }
   2003                     return new AtCommandResult(AtCommandResult.OK);
   2004                 } else {
   2005                     // No current call - redial last number
   2006                     return redial();
   2007                 }
   2008             }
   2009             @Override
   2010             public AtCommandResult handleActionCommand() {
   2011                 return headsetButtonPress();
   2012             }
   2013             @Override
   2014             public AtCommandResult handleSetCommand(Object[] args) {
   2015                 return headsetButtonPress();
   2016             }
   2017         });
   2018     }
   2019 
   2020     /**
   2021      * Register AT Command handlers to implement the Handsfree profile
   2022      */
   2023     private void initializeHandsfreeAtParser() {
   2024         if (VDBG) log("Registering Handsfree AT commands");
   2025         AtParser parser = mHeadset.getAtParser();
   2026         final Phone phone = mCM.getDefaultPhone();
   2027 
   2028         // Answer
   2029         parser.register('A', new AtCommandHandler() {
   2030             @Override
   2031             public AtCommandResult handleBasicCommand(String args) {
   2032                 sendURC("OK");
   2033                 mBluetoothPhoneState.stopRing();
   2034                 PhoneUtils.answerCall(mCM.getFirstActiveRingingCall());
   2035                 return new AtCommandResult(AtCommandResult.UNSOLICITED);
   2036             }
   2037         });
   2038         parser.register('D', new AtCommandHandler() {
   2039             @Override
   2040             public AtCommandResult handleBasicCommand(String args) {
   2041                 if (args.length() > 0) {
   2042                     if (args.charAt(0) == '>') {
   2043                         // Yuck - memory dialling requested.
   2044                         // Just dial last number for now
   2045                         if (args.startsWith(">9999")) {   // for PTS test
   2046                             return new AtCommandResult(AtCommandResult.ERROR);
   2047                         }
   2048                         return redial();
   2049                     } else {
   2050                         // Send terminateScoUsingVirtualVoiceCall
   2051                         terminateScoUsingVirtualVoiceCall();
   2052                         // Remove trailing ';'
   2053                         if (args.charAt(args.length() - 1) == ';') {
   2054                             args = args.substring(0, args.length() - 1);
   2055                         }
   2056 
   2057                         args = PhoneNumberUtils.convertPreDial(args);
   2058 
   2059                         Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
   2060                                 Uri.fromParts(Constants.SCHEME_TEL, args, null));
   2061                         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   2062                         mContext.startActivity(intent);
   2063 
   2064                         expectCallStart();
   2065                         return new AtCommandResult(AtCommandResult.UNSOLICITED);  // send nothing
   2066                     }
   2067                 }
   2068                 return new AtCommandResult(AtCommandResult.ERROR);
   2069             }
   2070         });
   2071 
   2072         // Hang-up command
   2073         parser.register("+CHUP", new AtCommandHandler() {
   2074             @Override
   2075             public AtCommandResult handleActionCommand() {
   2076                 sendURC("OK");
   2077                 if (isVirtualCallInProgress()) {
   2078                     terminateScoUsingVirtualVoiceCall();
   2079                 } else {
   2080                     if (mCM.hasActiveFgCall()) {
   2081                         PhoneUtils.hangupActiveCall(mCM.getActiveFgCall());
   2082                     } else if (mCM.hasActiveRingingCall()) {
   2083                         PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());
   2084                     } else if (mCM.hasActiveBgCall()) {
   2085                         PhoneUtils.hangupHoldingCall(mCM.getFirstActiveBgCall());
   2086                     }
   2087                 }
   2088                 return new AtCommandResult(AtCommandResult.UNSOLICITED);
   2089             }
   2090         });
   2091 
   2092         // Bluetooth Retrieve Supported Features command
   2093         parser.register("+BRSF", new AtCommandHandler() {
   2094             private AtCommandResult sendBRSF() {
   2095                 return new AtCommandResult("+BRSF: " + mLocalBrsf);
   2096             }
   2097             @Override
   2098             public AtCommandResult handleSetCommand(Object[] args) {
   2099                 // AT+BRSF=<handsfree supported features bitmap>
   2100                 // Handsfree is telling us which features it supports. We
   2101                 // send the features we support
   2102                 if (args.length == 1 && (args[0] instanceof Integer)) {
   2103                     mRemoteBrsf = (Integer) args[0];
   2104                 } else {
   2105                     Log.w(TAG, "HF didn't sent BRSF assuming 0");
   2106                 }
   2107                 return sendBRSF();
   2108             }
   2109             @Override
   2110             public AtCommandResult handleActionCommand() {
   2111                 // This seems to be out of spec, but lets do the nice thing
   2112                 return sendBRSF();
   2113             }
   2114             @Override
   2115             public AtCommandResult handleReadCommand() {
   2116                 // This seems to be out of spec, but lets do the nice thing
   2117                 return sendBRSF();
   2118             }
   2119         });
   2120 
   2121         // Call waiting notification on/off
   2122         parser.register("+CCWA", new AtCommandHandler() {
   2123             @Override
   2124             public AtCommandResult handleActionCommand() {
   2125                 // Seems to be out of spec, but lets return nicely
   2126                 return new AtCommandResult(AtCommandResult.OK);
   2127             }
   2128             @Override
   2129             public AtCommandResult handleReadCommand() {
   2130                 // Call waiting is always on
   2131                 return new AtCommandResult("+CCWA: 1");
   2132             }
   2133             @Override
   2134             public AtCommandResult handleSetCommand(Object[] args) {
   2135                 // AT+CCWA=<n>
   2136                 // Handsfree is trying to enable/disable call waiting. We
   2137                 // cannot disable in the current implementation.
   2138                 return new AtCommandResult(AtCommandResult.OK);
   2139             }
   2140             @Override
   2141             public AtCommandResult handleTestCommand() {
   2142                 // Request for range of supported CCWA paramters
   2143                 return new AtCommandResult("+CCWA: (\"n\",(1))");
   2144             }
   2145         });
   2146 
   2147         // Mobile Equipment Event Reporting enable/disable command
   2148         // Of the full 3GPP syntax paramters (mode, keyp, disp, ind, bfr) we
   2149         // only support paramter ind (disable/enable evert reporting using
   2150         // +CDEV)
   2151         parser.register("+CMER", new AtCommandHandler() {
   2152             @Override
   2153             public AtCommandResult handleReadCommand() {
   2154                 return new AtCommandResult(
   2155                         "+CMER: 3,0,0," + (mIndicatorsEnabled ? "1" : "0"));
   2156             }
   2157             @Override
   2158             public AtCommandResult handleSetCommand(Object[] args) {
   2159                 if (args.length < 4) {
   2160                     // This is a syntax error
   2161                     return new AtCommandResult(AtCommandResult.ERROR);
   2162                 } else if (args[0].equals(3) && args[1].equals(0) &&
   2163                            args[2].equals(0)) {
   2164                     boolean valid = false;
   2165                     if (args[3].equals(0)) {
   2166                         mIndicatorsEnabled = false;
   2167                         valid = true;
   2168                     } else if (args[3].equals(1)) {
   2169                         mIndicatorsEnabled = true;
   2170                         valid = true;
   2171                     }
   2172                     if (valid) {
   2173                         if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) == 0x0) {
   2174                             mServiceConnectionEstablished = true;
   2175                             sendURC("OK");  // send immediately, then initiate audio
   2176                             if (isIncallAudio()) {
   2177                                 audioOn();
   2178                             } else if (mCM.getFirstActiveRingingCall().isRinging()) {
   2179                                 // need to update HS with RING cmd when single
   2180                                 // ringing call exist
   2181                                 mBluetoothPhoneState.ring();
   2182                             }
   2183                             // only send OK once
   2184                             return new AtCommandResult(AtCommandResult.UNSOLICITED);
   2185                         } else {
   2186                             return new AtCommandResult(AtCommandResult.OK);
   2187                         }
   2188                     }
   2189                 }
   2190                 return reportCmeError(BluetoothCmeError.OPERATION_NOT_SUPPORTED);
   2191             }
   2192             @Override
   2193             public AtCommandResult handleTestCommand() {
   2194                 return new AtCommandResult("+CMER: (3),(0),(0),(0-1)");
   2195             }
   2196         });
   2197 
   2198         // Mobile Equipment Error Reporting enable/disable
   2199         parser.register("+CMEE", new AtCommandHandler() {
   2200             @Override
   2201             public AtCommandResult handleActionCommand() {
   2202                 // out of spec, assume they want to enable
   2203                 mCmee = true;
   2204                 return new AtCommandResult(AtCommandResult.OK);
   2205             }
   2206             @Override
   2207             public AtCommandResult handleReadCommand() {
   2208                 return new AtCommandResult("+CMEE: " + (mCmee ? "1" : "0"));
   2209             }
   2210             @Override
   2211             public AtCommandResult handleSetCommand(Object[] args) {
   2212                 // AT+CMEE=<n>
   2213                 if (args.length == 0) {
   2214                     // <n> ommitted - default to 0
   2215                     mCmee = false;
   2216                     return new AtCommandResult(AtCommandResult.OK);
   2217                 } else if (!(args[0] instanceof Integer)) {
   2218                     // Syntax error
   2219                     return new AtCommandResult(AtCommandResult.ERROR);
   2220                 } else {
   2221                     mCmee = ((Integer)args[0] == 1);
   2222                     return new AtCommandResult(AtCommandResult.OK);
   2223                 }
   2224             }
   2225             @Override
   2226             public AtCommandResult handleTestCommand() {
   2227                 // Probably not required but spec, but no harm done
   2228                 return new AtCommandResult("+CMEE: (0-1)");
   2229             }
   2230         });
   2231 
   2232         // Bluetooth Last Dialled Number
   2233         parser.register("+BLDN", new AtCommandHandler() {
   2234             @Override
   2235             public AtCommandResult handleActionCommand() {
   2236                 return redial();
   2237             }
   2238         });
   2239 
   2240         // Indicator Update command
   2241         parser.register("+CIND", new AtCommandHandler() {
   2242             @Override
   2243             public AtCommandResult handleReadCommand() {
   2244                 return mBluetoothPhoneState.toCindResult();
   2245             }
   2246             @Override
   2247             public AtCommandResult handleTestCommand() {
   2248                 return mBluetoothPhoneState.getCindTestResult();
   2249             }
   2250         });
   2251 
   2252         // Query Signal Quality (legacy)
   2253         parser.register("+CSQ", new AtCommandHandler() {
   2254             @Override
   2255             public AtCommandResult handleActionCommand() {
   2256                 return mBluetoothPhoneState.toCsqResult();
   2257             }
   2258         });
   2259 
   2260         // Query network registration state
   2261         parser.register("+CREG", new AtCommandHandler() {
   2262             @Override
   2263             public AtCommandResult handleReadCommand() {
   2264                 return new AtCommandResult(mBluetoothPhoneState.toCregString());
   2265             }
   2266         });
   2267 
   2268         // Send DTMF. I don't know if we are also expected to play the DTMF tone
   2269         // locally, right now we don't
   2270         parser.register("+VTS", new AtCommandHandler() {
   2271             @Override
   2272             public AtCommandResult handleSetCommand(Object[] args) {
   2273                 if (args.length >= 1) {
   2274                     char c;
   2275                     if (args[0] instanceof Integer) {
   2276                         c = ((Integer) args[0]).toString().charAt(0);
   2277                     } else {
   2278                         c = ((String) args[0]).charAt(0);
   2279                     }
   2280                     if (isValidDtmf(c)) {
   2281                         phone.sendDtmf(c);
   2282                         return new AtCommandResult(AtCommandResult.OK);
   2283                     }
   2284                 }
   2285                 return new AtCommandResult(AtCommandResult.ERROR);
   2286             }
   2287             private boolean isValidDtmf(char c) {
   2288                 switch (c) {
   2289                 case '#':
   2290                 case '*':
   2291                     return true;
   2292                 default:
   2293                     if (Character.digit(c, 14) != -1) {
   2294                         return true;  // 0-9 and A-D
   2295                     }
   2296                     return false;
   2297                 }
   2298             }
   2299         });
   2300 
   2301         // List calls
   2302         parser.register("+CLCC", new AtCommandHandler() {
   2303             @Override
   2304             public AtCommandResult handleActionCommand() {
   2305                 int phoneType = phone.getPhoneType();
   2306                 // Handsfree carkits expect that +CLCC is properly responded to.
   2307                 // Hence we ensure that a proper response is sent for the virtual call too.
   2308                 if (isVirtualCallInProgress()) {
   2309                     String number = phone.getLine1Number();
   2310                     AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
   2311                     String args;
   2312                     if (number == null) {
   2313                         args = "+CLCC: 1,0,0,0,0,\"\",0";
   2314                     }
   2315                     else
   2316                     {
   2317                         args = "+CLCC: 1,0,0,0,0,\"" + number + "\"," +
   2318                                   PhoneNumberUtils.toaFromString(number);
   2319                     }
   2320                     result.addResponse(args);
   2321                     return result;
   2322                 }
   2323                 if (phoneType == Phone.PHONE_TYPE_CDMA) {
   2324                     return cdmaGetClccResult();
   2325                 } else if (phoneType == Phone.PHONE_TYPE_GSM) {
   2326                     return gsmGetClccResult();
   2327                 } else {
   2328                     throw new IllegalStateException("Unexpected phone type: " + phoneType);
   2329                 }
   2330             }
   2331         });
   2332 
   2333         // Call Hold and Multiparty Handling command
   2334         parser.register("+CHLD", new AtCommandHandler() {
   2335             @Override
   2336             public AtCommandResult handleSetCommand(Object[] args) {
   2337                 int phoneType = phone.getPhoneType();
   2338                 Call ringingCall = mCM.getFirstActiveRingingCall();
   2339                 Call backgroundCall = mCM.getFirstActiveBgCall();
   2340 
   2341                 if (args.length >= 1) {
   2342                     if (args[0].equals(0)) {
   2343                         boolean result;
   2344                         if (ringingCall.isRinging()) {
   2345                             result = PhoneUtils.hangupRingingCall(ringingCall);
   2346                         } else {
   2347                             result = PhoneUtils.hangupHoldingCall(backgroundCall);
   2348                         }
   2349                         if (result) {
   2350                             return new AtCommandResult(AtCommandResult.OK);
   2351                         } else {
   2352                             return new AtCommandResult(AtCommandResult.ERROR);
   2353                         }
   2354                     } else if (args[0].equals(1)) {
   2355                         if (phoneType == Phone.PHONE_TYPE_CDMA) {
   2356                             if (ringingCall.isRinging()) {
   2357                                 // Hangup the active call and then answer call waiting call.
   2358                                 if (VDBG) log("CHLD:1 Callwaiting Answer call");
   2359                                 PhoneUtils.hangupRingingAndActive(phone);
   2360                             } else {
   2361                                 // If there is no Call waiting then just hangup
   2362                                 // the active call. In CDMA this mean that the complete
   2363                                 // call session would be ended
   2364                                 if (VDBG) log("CHLD:1 Hangup Call");
   2365                                 PhoneUtils.hangup(PhoneApp.getInstance().mCM);
   2366                             }
   2367                             return new AtCommandResult(AtCommandResult.OK);
   2368                         } else if (phoneType == Phone.PHONE_TYPE_GSM) {
   2369                             // Hangup active call, answer held call
   2370                             if (PhoneUtils.answerAndEndActive(
   2371                                     PhoneApp.getInstance().mCM, ringingCall)) {
   2372                                 return new AtCommandResult(AtCommandResult.OK);
   2373                             } else {
   2374                                 return new AtCommandResult(AtCommandResult.ERROR);
   2375                             }
   2376                         } else {
   2377                             throw new IllegalStateException("Unexpected phone type: " + phoneType);
   2378                         }
   2379                     } else if (args[0].equals(2)) {
   2380                         sendURC("OK");
   2381                         if (phoneType == Phone.PHONE_TYPE_CDMA) {
   2382                             // For CDMA, the way we switch to a new incoming call is by
   2383                             // calling PhoneUtils.answerCall(). switchAndHoldActive() won't
   2384                             // properly update the call state within telephony.
   2385                             // If the Phone state is already in CONF_CALL then we simply send
   2386                             // a flash cmd by calling switchHoldingAndActive()
   2387                             if (ringingCall.isRinging()) {
   2388                                 if (VDBG) log("CHLD:2 Callwaiting Answer call");
   2389                                 PhoneUtils.answerCall(ringingCall);
   2390                                 PhoneUtils.setMute(false);
   2391                                 // Setting the second callers state flag to TRUE (i.e. active)
   2392                                 cdmaSetSecondCallState(true);
   2393                             } else if (PhoneApp.getInstance().cdmaPhoneCallState
   2394                                     .getCurrentCallState()
   2395                                     == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
   2396                                 if (VDBG) log("CHLD:2 Swap Calls");
   2397                                 PhoneUtils.switchHoldingAndActive(backgroundCall);
   2398                                 // Toggle the second callers active state flag
   2399                                 cdmaSwapSecondCallState();
   2400                             }
   2401                         } else if (phoneType == Phone.PHONE_TYPE_GSM) {
   2402                             PhoneUtils.switchHoldingAndActive(backgroundCall);
   2403                         } else {
   2404                             throw new IllegalStateException("Unexpected phone type: " + phoneType);
   2405                         }
   2406                         return new AtCommandResult(AtCommandResult.UNSOLICITED);
   2407                     } else if (args[0].equals(3)) {
   2408                         sendURC("OK");
   2409                         if (phoneType == Phone.PHONE_TYPE_CDMA) {
   2410                             CdmaPhoneCallState.PhoneCallState state =
   2411                                 PhoneApp.getInstance().cdmaPhoneCallState.getCurrentCallState();
   2412                             // For CDMA, we need to check if the call is in THRWAY_ACTIVE state
   2413                             if (state == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
   2414                                 if (VDBG) log("CHLD:3 Merge Calls");
   2415                                 PhoneUtils.mergeCalls();
   2416                             } else if (state == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
   2417                                 // State is CONF_CALL already and we are getting a merge call
   2418                                 // This can happen when CONF_CALL was entered from a Call Waiting
   2419                                 mBluetoothPhoneState.updateCallHeld();
   2420                             }
   2421                         } else if (phoneType == Phone.PHONE_TYPE_GSM) {
   2422                             if (mCM.hasActiveFgCall() && mCM.hasActiveBgCall()) {
   2423                                 PhoneUtils.mergeCalls();
   2424                             }
   2425                         } else {
   2426                             throw new IllegalStateException("Unexpected phone type: " + phoneType);
   2427                         }
   2428                         return new AtCommandResult(AtCommandResult.UNSOLICITED);
   2429                     }
   2430                 }
   2431                 return new AtCommandResult(AtCommandResult.ERROR);
   2432             }
   2433             @Override
   2434             public AtCommandResult handleTestCommand() {
   2435                 mServiceConnectionEstablished = true;
   2436                 sendURC("+CHLD: (0,1,2,3)");
   2437                 sendURC("OK");  // send reply first, then connect audio
   2438                 if (isIncallAudio()) {
   2439                     audioOn();
   2440                 } else if (mCM.getFirstActiveRingingCall().isRinging()) {
   2441                     // need to update HS with RING when single ringing call exist
   2442                     mBluetoothPhoneState.ring();
   2443                 }
   2444                 // already replied
   2445                 return new AtCommandResult(AtCommandResult.UNSOLICITED);
   2446             }
   2447         });
   2448 
   2449         // Get Network operator name
   2450         parser.register("+COPS", new AtCommandHandler() {
   2451             @Override
   2452             public AtCommandResult handleReadCommand() {
   2453                 String operatorName = phone.getServiceState().getOperatorAlphaLong();
   2454                 if (operatorName != null) {
   2455                     if (operatorName.length() > 16) {
   2456                         operatorName = operatorName.substring(0, 16);
   2457                     }
   2458                     return new AtCommandResult(
   2459                             "+COPS: 0,0,\"" + operatorName + "\"");
   2460                 } else {
   2461                     return new AtCommandResult(
   2462                             "+COPS: 0");
   2463                 }
   2464             }
   2465             @Override
   2466             public AtCommandResult handleSetCommand(Object[] args) {
   2467                 // Handsfree only supports AT+COPS=3,0
   2468                 if (args.length != 2 || !(args[0] instanceof Integer)
   2469                     || !(args[1] instanceof Integer)) {
   2470                     // syntax error
   2471                     return new AtCommandResult(AtCommandResult.ERROR);
   2472                 } else if ((Integer)args[0] != 3 || (Integer)args[1] != 0) {
   2473                     return reportCmeError(BluetoothCmeError.OPERATION_NOT_SUPPORTED);
   2474                 } else {
   2475                     return new AtCommandResult(AtCommandResult.OK);
   2476                 }
   2477             }
   2478             @Override
   2479             public AtCommandResult handleTestCommand() {
   2480                 // Out of spec, but lets be friendly
   2481                 return new AtCommandResult("+COPS: (3),(0)");
   2482             }
   2483         });
   2484 
   2485         // Mobile PIN
   2486         // AT+CPIN is not in the handsfree spec (although it is in 3GPP)
   2487         parser.register("+CPIN", new AtCommandHandler() {
   2488             @Override
   2489             public AtCommandResult handleReadCommand() {
   2490                 return new AtCommandResult("+CPIN: READY");
   2491             }
   2492         });
   2493 
   2494         // Bluetooth Response and Hold
   2495         // Only supported on PDC (Japan) and CDMA networks.
   2496         parser.register("+BTRH", new AtCommandHandler() {
   2497             @Override
   2498             public AtCommandResult handleReadCommand() {
   2499                 // Replying with just OK indicates no response and hold
   2500                 // features in use now
   2501                 return new AtCommandResult(AtCommandResult.OK);
   2502             }
   2503             @Override
   2504             public AtCommandResult handleSetCommand(Object[] args) {
   2505                 // Neeed PDC or CDMA
   2506                 return new AtCommandResult(AtCommandResult.ERROR);
   2507             }
   2508         });
   2509 
   2510         // Request International Mobile Subscriber Identity (IMSI)
   2511         // Not in bluetooth handset spec
   2512         parser.register("+CIMI", new AtCommandHandler() {
   2513             @Override
   2514             public AtCommandResult handleActionCommand() {
   2515                 // AT+CIMI
   2516                 String imsi = phone.getSubscriberId();
   2517                 if (imsi == null || imsi.length() == 0) {
   2518                     return reportCmeError(BluetoothCmeError.SIM_FAILURE);
   2519                 } else {
   2520                     return new AtCommandResult(imsi);
   2521                 }
   2522             }
   2523         });
   2524 
   2525         // Calling Line Identification Presentation
   2526         parser.register("+CLIP", new AtCommandHandler() {
   2527             @Override
   2528             public AtCommandResult handleReadCommand() {
   2529                 // Currently assumes the network is provisioned for CLIP
   2530                 return new AtCommandResult("+CLIP: " + (mClip ? "1" : "0") + ",1");
   2531             }
   2532             @Override
   2533             public AtCommandResult handleSetCommand(Object[] args) {
   2534                 // AT+CLIP=<n>
   2535                 if (args.length >= 1 && (args[0].equals(0) || args[0].equals(1))) {
   2536                     mClip = args[0].equals(1);
   2537                     return new AtCommandResult(AtCommandResult.OK);
   2538                 } else {
   2539                     return new AtCommandResult(AtCommandResult.ERROR);
   2540                 }
   2541             }
   2542             @Override
   2543             public AtCommandResult handleTestCommand() {
   2544                 return new AtCommandResult("+CLIP: (0-1)");
   2545             }
   2546         });
   2547 
   2548         // AT+CGSN - Returns the device IMEI number.
   2549         parser.register("+CGSN", new AtCommandHandler() {
   2550             @Override
   2551             public AtCommandResult handleActionCommand() {
   2552                 // Get the IMEI of the device.
   2553                 // phone will not be NULL at this point.
   2554                 return new AtCommandResult("+CGSN: " + phone.getDeviceId());
   2555             }
   2556         });
   2557 
   2558         // AT+CGMM - Query Model Information
   2559         parser.register("+CGMM", new AtCommandHandler() {
   2560             @Override
   2561             public AtCommandResult handleActionCommand() {
   2562                 // Return the Model Information.
   2563                 String model = SystemProperties.get("ro.product.model");
   2564                 if (model != null) {
   2565                     return new AtCommandResult("+CGMM: " + model);
   2566                 } else {
   2567                     return new AtCommandResult(AtCommandResult.ERROR);
   2568                 }
   2569             }
   2570         });
   2571 
   2572         // AT+CGMI - Query Manufacturer Information
   2573         parser.register("+CGMI", new AtCommandHandler() {
   2574             @Override
   2575             public AtCommandResult handleActionCommand() {
   2576                 // Return the Model Information.
   2577                 String manuf = SystemProperties.get("ro.product.manufacturer");
   2578                 if (manuf != null) {
   2579                     return new AtCommandResult("+CGMI: " + manuf);
   2580                 } else {
   2581                     return new AtCommandResult(AtCommandResult.ERROR);
   2582                 }
   2583             }
   2584         });
   2585 
   2586         // Noise Reduction and Echo Cancellation control
   2587         parser.register("+NREC", new AtCommandHandler() {
   2588             @Override
   2589             public AtCommandResult handleSetCommand(Object[] args) {
   2590                 if (args[0].equals(0)) {
   2591                     mAudioManager.setParameters(HEADSET_NREC+"=off");
   2592                     return new AtCommandResult(AtCommandResult.OK);
   2593                 } else if (args[0].equals(1)) {
   2594                     mAudioManager.setParameters(HEADSET_NREC+"=on");
   2595                     return new AtCommandResult(AtCommandResult.OK);
   2596                 }
   2597                 return new AtCommandResult(AtCommandResult.ERROR);
   2598             }
   2599         });
   2600 
   2601         // Voice recognition (dialing)
   2602         parser.register("+BVRA", new AtCommandHandler() {
   2603             @Override
   2604             public AtCommandResult handleSetCommand(Object[] args) {
   2605                 if (!BluetoothHeadset.isBluetoothVoiceDialingEnabled(mContext)) {
   2606                     return new AtCommandResult(AtCommandResult.ERROR);
   2607                 }
   2608                 if (args.length >= 1 && args[0].equals(1)) {
   2609                     synchronized (BluetoothHandsfree.this) {
   2610                         if (!isVoiceRecognitionInProgress() &&
   2611                             !isCellularCallInProgress() &&
   2612                             !isVirtualCallInProgress()) {
   2613                             try {
   2614                                 mContext.startActivity(sVoiceCommandIntent);
   2615                             } catch (ActivityNotFoundException e) {
   2616                                 return new AtCommandResult(AtCommandResult.ERROR);
   2617                             }
   2618                             expectVoiceRecognition();
   2619                         }
   2620                     }
   2621                     return new AtCommandResult(AtCommandResult.UNSOLICITED);  // send nothing yet
   2622                 } else if (args.length >= 1 && args[0].equals(0)) {
   2623                     if (isVoiceRecognitionInProgress()) {
   2624                         audioOff();
   2625                     }
   2626                     return new AtCommandResult(AtCommandResult.OK);
   2627                 }
   2628                 return new AtCommandResult(AtCommandResult.ERROR);
   2629             }
   2630             @Override
   2631             public AtCommandResult handleTestCommand() {
   2632                 return new AtCommandResult("+BVRA: (0-1)");
   2633             }
   2634         });
   2635 
   2636         // Retrieve Subscriber Number
   2637         parser.register("+CNUM", new AtCommandHandler() {
   2638             @Override
   2639             public AtCommandResult handleActionCommand() {
   2640                 String number = phone.getLine1Number();
   2641                 if (number == null) {
   2642                     return new AtCommandResult(AtCommandResult.OK);
   2643                 }
   2644                 return new AtCommandResult("+CNUM: ,\"" + number + "\"," +
   2645                         PhoneNumberUtils.toaFromString(number) + ",,4");
   2646             }
   2647         });
   2648 
   2649         // Microphone Gain
   2650         parser.register("+VGM", new AtCommandHandler() {
   2651             @Override
   2652             public AtCommandResult handleSetCommand(Object[] args) {
   2653                 // AT+VGM=<gain>    in range [0,15]
   2654                 // Headset/Handsfree is reporting its current gain setting
   2655                 return new AtCommandResult(AtCommandResult.OK);
   2656             }
   2657         });
   2658 
   2659         // Speaker Gain
   2660         parser.register("+VGS", new AtCommandHandler() {
   2661             @Override
   2662             public AtCommandResult handleSetCommand(Object[] args) {
   2663                 // AT+VGS=<gain>    in range [0,15]
   2664                 if (args.length != 1 || !(args[0] instanceof Integer)) {
   2665                     return new AtCommandResult(AtCommandResult.ERROR);
   2666                 }
   2667                 mScoGain = (Integer) args[0];
   2668                 int flag =  mAudioManager.isBluetoothScoOn() ? AudioManager.FLAG_SHOW_UI:0;
   2669 
   2670                 mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, mScoGain, flag);
   2671                 return new AtCommandResult(AtCommandResult.OK);
   2672             }
   2673         });
   2674 
   2675         // Phone activity status
   2676         parser.register("+CPAS", new AtCommandHandler() {
   2677             @Override
   2678             public AtCommandResult handleActionCommand() {
   2679                 int status = 0;
   2680                 switch (mCM.getState()) {
   2681                 case IDLE:
   2682                     status = 0;
   2683                     break;
   2684                 case RINGING:
   2685                     status = 3;
   2686                     break;
   2687                 case OFFHOOK:
   2688                     status = 4;
   2689                     break;
   2690                 }
   2691                 return new AtCommandResult("+CPAS: " + status);
   2692             }
   2693         });
   2694 
   2695         mPhonebook.register(parser);
   2696     }
   2697 
   2698     public void sendScoGainUpdate(int gain) {
   2699         if (mScoGain != gain && (mRemoteBrsf & BRSF_HF_REMOTE_VOL_CONTROL) != 0x0) {
   2700             sendURC("+VGS:" + gain);
   2701             mScoGain = gain;
   2702         }
   2703     }
   2704 
   2705     public AtCommandResult reportCmeError(int error) {
   2706         if (mCmee) {
   2707             AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
   2708             result.addResponse("+CME ERROR: " + error);
   2709             return result;
   2710         } else {
   2711             return new AtCommandResult(AtCommandResult.ERROR);
   2712         }
   2713     }
   2714 
   2715     private static final int START_CALL_TIMEOUT = 10000;  // ms
   2716 
   2717     private synchronized void expectCallStart() {
   2718         mWaitingForCallStart = true;
   2719         Message msg = Message.obtain(mHandler, CHECK_CALL_STARTED);
   2720         mHandler.sendMessageDelayed(msg, START_CALL_TIMEOUT);
   2721         if (!mStartCallWakeLock.isHeld()) {
   2722             mStartCallWakeLock.acquire(START_CALL_TIMEOUT);
   2723         }
   2724     }
   2725 
   2726     private synchronized void callStarted() {
   2727         if (mWaitingForCallStart) {
   2728             mWaitingForCallStart = false;
   2729             sendURC("OK");
   2730             if (mStartCallWakeLock.isHeld()) {
   2731                 mStartCallWakeLock.release();
   2732             }
   2733         }
   2734     }
   2735 
   2736     private static final int START_VOICE_RECOGNITION_TIMEOUT = 5000;  // ms
   2737 
   2738     private synchronized void expectVoiceRecognition() {
   2739         mWaitingForVoiceRecognition = true;
   2740         Message msg = Message.obtain(mHandler, CHECK_VOICE_RECOGNITION_STARTED);
   2741         mHandler.sendMessageDelayed(msg, START_VOICE_RECOGNITION_TIMEOUT);
   2742         if (!mStartVoiceRecognitionWakeLock.isHeld()) {
   2743             mStartVoiceRecognitionWakeLock.acquire(START_VOICE_RECOGNITION_TIMEOUT);
   2744         }
   2745     }
   2746 
   2747     /* package */ synchronized boolean startVoiceRecognition() {
   2748 
   2749         if  ((isCellularCallInProgress()) ||
   2750              (isVirtualCallInProgress()) ||
   2751              mVoiceRecognitionStarted) {
   2752             Log.e(TAG, "startVoiceRecognition: Call in progress");
   2753             return false;
   2754         }
   2755 
   2756         mVoiceRecognitionStarted = true;
   2757 
   2758         if (mWaitingForVoiceRecognition) {
   2759             // HF initiated
   2760             mWaitingForVoiceRecognition = false;
   2761             sendURC("OK");
   2762         } else {
   2763             // AG initiated
   2764             sendURC("+BVRA: 1");
   2765         }
   2766         boolean ret = audioOn();
   2767         if (ret == false) {
   2768             mVoiceRecognitionStarted = false;
   2769         }
   2770         if (mStartVoiceRecognitionWakeLock.isHeld()) {
   2771             mStartVoiceRecognitionWakeLock.release();
   2772         }
   2773         return ret;
   2774     }
   2775 
   2776     /* package */ synchronized boolean stopVoiceRecognition() {
   2777 
   2778         if (!isVoiceRecognitionInProgress()) {
   2779             return false;
   2780         }
   2781 
   2782         mVoiceRecognitionStarted = false;
   2783 
   2784         sendURC("+BVRA: 0");
   2785         audioOff();
   2786         return true;
   2787     }
   2788 
   2789     // Voice Recognition in Progress
   2790     private boolean isVoiceRecognitionInProgress() {
   2791         return (mVoiceRecognitionStarted || mWaitingForVoiceRecognition);
   2792     }
   2793 
   2794     /*
   2795      * This class broadcasts vendor-specific commands + arguments to interested receivers.
   2796      */
   2797     private class VendorSpecificCommandHandler extends AtCommandHandler {
   2798 
   2799         private String mCommandName;
   2800 
   2801         private int mCompanyId;
   2802 
   2803         private VendorSpecificCommandHandler(String commandName, int companyId) {
   2804             mCommandName = commandName;
   2805             mCompanyId = companyId;
   2806         }
   2807 
   2808         @Override
   2809         public AtCommandResult handleReadCommand() {
   2810             return new AtCommandResult(AtCommandResult.ERROR);
   2811         }
   2812 
   2813         @Override
   2814         public AtCommandResult handleTestCommand() {
   2815             return new AtCommandResult(AtCommandResult.ERROR);
   2816         }
   2817 
   2818         @Override
   2819         public AtCommandResult handleActionCommand() {
   2820             return new AtCommandResult(AtCommandResult.ERROR);
   2821         }
   2822 
   2823         @Override
   2824         public AtCommandResult handleSetCommand(Object[] arguments) {
   2825             broadcastVendorSpecificEventIntent(mCommandName,
   2826                                                mCompanyId,
   2827                                                BluetoothHeadset.AT_CMD_TYPE_SET,
   2828                                                arguments,
   2829                                                mHeadset.getRemoteDevice());
   2830             return new AtCommandResult(AtCommandResult.OK);
   2831         }
   2832     }
   2833 
   2834     private boolean inDebug() {
   2835         return DBG && SystemProperties.getBoolean(DebugThread.DEBUG_HANDSFREE, false);
   2836     }
   2837 
   2838     private boolean allowAudioAnytime() {
   2839         return inDebug() && SystemProperties.getBoolean(DebugThread.DEBUG_HANDSFREE_AUDIO_ANYTIME,
   2840                 false);
   2841     }
   2842 
   2843     private void startDebug() {
   2844         if (DBG && mDebugThread == null) {
   2845             mDebugThread = new DebugThread();
   2846             mDebugThread.start();
   2847         }
   2848     }
   2849 
   2850     private void stopDebug() {
   2851         if (mDebugThread != null) {
   2852             mDebugThread.interrupt();
   2853             mDebugThread = null;
   2854         }
   2855     }
   2856 
   2857     // VirtualCall SCO support
   2858     //
   2859 
   2860     // Cellular call in progress
   2861     private boolean isCellularCallInProgress() {
   2862         if (mCM.hasActiveFgCall() || mCM.hasActiveRingingCall()) return true;
   2863         return false;
   2864     }
   2865 
   2866     // Virtual Call in Progress
   2867     private boolean isVirtualCallInProgress() {
   2868         return mVirtualCallStarted;
   2869     }
   2870 
   2871     void setVirtualCallInProgress(boolean state) {
   2872         mVirtualCallStarted = state;
   2873     }
   2874 
   2875     //NOTE: Currently the VirtualCall API does not allow the application to initiate a call
   2876     // transfer. Call transfer may be initiated from the handsfree device and this is handled by
   2877     // the VirtualCall API
   2878     synchronized boolean initiateScoUsingVirtualVoiceCall() {
   2879         if (DBG) log("initiateScoUsingVirtualVoiceCall: Received");
   2880         // 1. Check if the SCO state is idle
   2881         if (isCellularCallInProgress() || isVoiceRecognitionInProgress()) {
   2882             Log.e(TAG, "initiateScoUsingVirtualVoiceCall: Call in progress");
   2883             return false;
   2884         }
   2885 
   2886         // 2. Perform outgoing call setup procedure
   2887         if (mBluetoothPhoneState.sendUpdate() && !isVirtualCallInProgress()) {
   2888             AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
   2889             // outgoing call
   2890             result.addResponse("+CIEV: 3,2");
   2891             result.addResponse("+CIEV: 2,1");
   2892             result.addResponse("+CIEV: 3,0");
   2893             sendURC(result.toString());
   2894             if (DBG) Log.d(TAG, "initiateScoUsingVirtualVoiceCall: Sent Call-setup procedure");
   2895         }
   2896 
   2897         mVirtualCallStarted = true;
   2898 
   2899         // 3. Open the Audio Connection
   2900         if (audioOn() == false) {
   2901             log("initiateScoUsingVirtualVoiceCall: audioON failed");
   2902             terminateScoUsingVirtualVoiceCall();
   2903             return false;
   2904         }
   2905 
   2906         mAudioPossible = true;
   2907 
   2908         // Done
   2909         if (DBG) log("initiateScoUsingVirtualVoiceCall: Done");
   2910         return true;
   2911     }
   2912 
   2913     synchronized boolean terminateScoUsingVirtualVoiceCall() {
   2914         if (DBG) log("terminateScoUsingVirtualVoiceCall: Received");
   2915 
   2916         if (!isVirtualCallInProgress()) {
   2917             return false;
   2918         }
   2919 
   2920         // 1. Release audio connection
   2921         audioOff();
   2922 
   2923         // 2. terminate call-setup
   2924         if (mBluetoothPhoneState.sendUpdate()) {
   2925             AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
   2926             // outgoing call
   2927             result.addResponse("+CIEV: 2,0");
   2928             sendURC(result.toString());
   2929             if (DBG) log("terminateScoUsingVirtualVoiceCall: Sent Call-setup procedure");
   2930         }
   2931         mVirtualCallStarted = false;
   2932         mAudioPossible = false;
   2933 
   2934         // Done
   2935         if (DBG) log("terminateScoUsingVirtualVoiceCall: Done");
   2936         return true;
   2937     }
   2938 
   2939 
   2940     /** Debug thread to read debug properties - runs when debug.bt.hfp is true
   2941      *  at the time a bluetooth handsfree device is connected. Debug properties
   2942      *  are polled and mock updates sent every 1 second */
   2943     private class DebugThread extends Thread {
   2944         /** Turns on/off handsfree profile debugging mode */
   2945         private static final String DEBUG_HANDSFREE = "debug.bt.hfp";
   2946 
   2947         /** Mock battery level change - use 0 to 5 */
   2948         private static final String DEBUG_HANDSFREE_BATTERY = "debug.bt.hfp.battery";
   2949 
   2950         /** Mock no cellular service when false */
   2951         private static final String DEBUG_HANDSFREE_SERVICE = "debug.bt.hfp.service";
   2952 
   2953         /** Mock cellular roaming when true */
   2954         private static final String DEBUG_HANDSFREE_ROAM = "debug.bt.hfp.roam";
   2955 
   2956         /** false to true transition will force an audio (SCO) connection to
   2957          *  be established. true to false will force audio to be disconnected
   2958          */
   2959         private static final String DEBUG_HANDSFREE_AUDIO = "debug.bt.hfp.audio";
   2960 
   2961         /** true allows incoming SCO connection out of call.
   2962          */
   2963         private static final String DEBUG_HANDSFREE_AUDIO_ANYTIME = "debug.bt.hfp.audio_anytime";
   2964 
   2965         /** Mock signal strength change in ASU - use 0 to 31 */
   2966         private static final String DEBUG_HANDSFREE_SIGNAL = "debug.bt.hfp.signal";
   2967 
   2968         /** Debug AT+CLCC: print +CLCC result */
   2969         private static final String DEBUG_HANDSFREE_CLCC = "debug.bt.hfp.clcc";
   2970 
   2971         /** Debug AT+BSIR - Send In Band Ringtones Unsolicited AT command.
   2972          * debug.bt.unsol.inband = 0 => AT+BSIR = 0 sent by the AG
   2973          * debug.bt.unsol.inband = 1 => AT+BSIR = 0 sent by the AG
   2974          * Other values are ignored.
   2975          */
   2976 
   2977         private static final String DEBUG_UNSOL_INBAND_RINGTONE =
   2978             "debug.bt.unsol.inband";
   2979 
   2980         @Override
   2981         public void run() {
   2982             boolean oldService = true;
   2983             boolean oldRoam = false;
   2984             boolean oldAudio = false;
   2985 
   2986             while (!isInterrupted() && inDebug()) {
   2987                 int batteryLevel = SystemProperties.getInt(DEBUG_HANDSFREE_BATTERY, -1);
   2988                 if (batteryLevel >= 0 && batteryLevel <= 5) {
   2989                     Intent intent = new Intent();
   2990                     intent.putExtra("level", batteryLevel);
   2991                     intent.putExtra("scale", 5);
   2992                     mBluetoothPhoneState.updateBatteryState(intent);
   2993                 }
   2994 
   2995                 boolean serviceStateChanged = false;
   2996                 if (SystemProperties.getBoolean(DEBUG_HANDSFREE_SERVICE, true) != oldService) {
   2997                     oldService = !oldService;
   2998                     serviceStateChanged = true;
   2999                 }
   3000                 if (SystemProperties.getBoolean(DEBUG_HANDSFREE_ROAM, false) != oldRoam) {
   3001                     oldRoam = !oldRoam;
   3002                     serviceStateChanged = true;
   3003                 }
   3004                 if (serviceStateChanged) {
   3005                     Bundle b = new Bundle();
   3006                     b.putInt("state", oldService ? 0 : 1);
   3007                     b.putBoolean("roaming", oldRoam);
   3008                     mBluetoothPhoneState.updateServiceState(true, ServiceState.newFromBundle(b));
   3009                 }
   3010 
   3011                 if (SystemProperties.getBoolean(DEBUG_HANDSFREE_AUDIO, false) != oldAudio) {
   3012                     oldAudio = !oldAudio;
   3013                     if (oldAudio) {
   3014                         audioOn();
   3015                     } else {
   3016                         audioOff();
   3017                     }
   3018                 }
   3019 
   3020                 int signalLevel = SystemProperties.getInt(DEBUG_HANDSFREE_SIGNAL, -1);
   3021                 if (signalLevel >= 0 && signalLevel <= 31) {
   3022                     SignalStrength signalStrength = new SignalStrength(signalLevel, -1, -1, -1,
   3023                             -1, -1, -1, true);
   3024                     Intent intent = new Intent();
   3025                     Bundle data = new Bundle();
   3026                     signalStrength.fillInNotifierBundle(data);
   3027                     intent.putExtras(data);
   3028                     mBluetoothPhoneState.updateSignalState(intent);
   3029                 }
   3030 
   3031                 if (SystemProperties.getBoolean(DEBUG_HANDSFREE_CLCC, false)) {
   3032                     log(gsmGetClccResult().toString());
   3033                 }
   3034                 try {
   3035                     sleep(1000);  // 1 second
   3036                 } catch (InterruptedException e) {
   3037                     break;
   3038                 }
   3039 
   3040                 int inBandRing =
   3041                     SystemProperties.getInt(DEBUG_UNSOL_INBAND_RINGTONE, -1);
   3042                 if (inBandRing == 0 || inBandRing == 1) {
   3043                     AtCommandResult result =
   3044                         new AtCommandResult(AtCommandResult.UNSOLICITED);
   3045                     result.addResponse("+BSIR: " + inBandRing);
   3046                     sendURC(result.toString());
   3047                 }
   3048             }
   3049         }
   3050     }
   3051 
   3052     public void cdmaSwapSecondCallState() {
   3053         if (VDBG) log("cdmaSetSecondCallState: Toggling mCdmaIsSecondCallActive");
   3054         mCdmaIsSecondCallActive = !mCdmaIsSecondCallActive;
   3055         mCdmaCallsSwapped = true;
   3056     }
   3057 
   3058     public void cdmaSetSecondCallState(boolean state) {
   3059         if (VDBG) log("cdmaSetSecondCallState: Setting mCdmaIsSecondCallActive to " + state);
   3060         mCdmaIsSecondCallActive = state;
   3061 
   3062         if (!mCdmaIsSecondCallActive) {
   3063             mCdmaCallsSwapped = false;
   3064         }
   3065     }
   3066 
   3067     private static void log(String msg) {
   3068         Log.d(TAG, msg);
   3069     }
   3070 }
   3071