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