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