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