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