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