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