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