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