BluetoothHandsfree.java revision c9d9ed30aa547b79b81adc13a4d148a003b6ee62
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                CdmaPhoneCallState.PhoneCallState currCdmaCallState =
685                        app.cdmaPhoneCallState.getCurrentCallState();
686
687                // In CDMA, the network does not provide any feedback to the phone when the
688                // 2nd MO call goes through the stages of DIALING > ALERTING -> ACTIVE
689                // we fake the sequence
690                if ((currCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
691                        && app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing()) {
692                    mAudioPossible = true;
693                    if (sendUpdate) {
694                        if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) {
695                            result.addResponse("+CIEV: 3,2");
696                            result.addResponse("+CIEV: 3,3");
697                            result.addResponse("+CIEV: 3,0");
698                        }
699                    }
700                }
701
702                // In CDMA, the network does not provide any feedback to the phone when a
703                // user merges a 3way call or swaps between two calls we need to send a
704                // CIEV response indicating that a call state got changed which should trigger a
705                // CLCC update request from the BT client.
706                if (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
707                    mAudioPossible = true;
708                    if (sendUpdate) {
709                        if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) {
710                            result.addResponse("+CIEV: 2,1");
711                            result.addResponse("+CIEV: 3,0");
712                        }
713                    }
714                }
715            }
716
717            boolean callsSwitched =
718                (callheld == 1 && ! (mBackgroundCall.getEarliestConnectTime() ==
719                    mBgndEarliestConnectionTime));
720
721            mBgndEarliestConnectionTime = mBackgroundCall.getEarliestConnectTime();
722
723            if (mCallheld != callheld || callsSwitched) {
724                mCallheld = callheld;
725                if (sendUpdate) {
726                    result.addResponse("+CIEV: 4," + mCallheld);
727                }
728            }
729
730            if (callsetup == 1 && callsetup != prevCallsetup) {
731                // new incoming call
732                String number = null;
733                int type = 128;
734                // find incoming phone number and type
735                if (connection == null) {
736                    connection = mRingingCall.getEarliestConnection();
737                    if (connection == null) {
738                        Log.e(TAG, "Could not get a handle on Connection object for new " +
739                              "incoming call");
740                    }
741                }
742                if (connection != null) {
743                    number = connection.getAddress();
744                    if (number != null) {
745                        type = PhoneNumberUtils.toaFromString(number);
746                    }
747                }
748                if (number == null) {
749                    number = "";
750                }
751                if ((call != 0 || callheld != 0) && sendUpdate) {
752                    // call waiting
753                    if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) {
754                        result.addResponse("+CCWA: \"" + number + "\"," + type);
755                        result.addResponse("+CIEV: 3," + callsetup);
756                    }
757                } else {
758                    // regular new incoming call
759                    mRingingNumber = number;
760                    mRingingType = type;
761                    mIgnoreRing = false;
762
763                    if ((mLocalBrsf & BRSF_AG_IN_BAND_RING) == 0x1) {
764                        audioOn();
765                    }
766                    result.addResult(ring());
767                }
768            }
769            sendURC(result.toString());
770        }
771
772        private AtCommandResult ring() {
773            if (!mIgnoreRing && mRingingCall.isRinging()) {
774                AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
775                result.addResponse("RING");
776                if (sendClipUpdate()) {
777                    result.addResponse("+CLIP: \"" + mRingingNumber + "\"," + mRingingType);
778                }
779
780                Message msg = mStateChangeHandler.obtainMessage(RING);
781                mStateChangeHandler.sendMessageDelayed(msg, 3000);
782                return result;
783            }
784            return null;
785        }
786
787        private synchronized String toCregString() {
788            return new String("+CREG: 1," + mStat);
789        }
790
791        private synchronized AtCommandResult toCindResult() {
792            AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
793            String status = "+CIND: " + mService + "," + mCall + "," + mCallsetup + "," +
794                            mCallheld + "," + mSignal + "," + mRoam + "," + mBattchg;
795            result.addResponse(status);
796            return result;
797        }
798
799        private synchronized AtCommandResult toCsqResult() {
800            AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
801            String status = "+CSQ: " + mRssi + ",99";
802            result.addResponse(status);
803            return result;
804        }
805
806
807        private synchronized AtCommandResult getCindTestResult() {
808            return new AtCommandResult("+CIND: (\"service\",(0-1))," + "(\"call\",(0-1))," +
809                        "(\"callsetup\",(0-3)),(\"callheld\",(0-2)),(\"signal\",(0-5))," +
810                        "(\"roam\",(0-1)),(\"battchg\",(0-5))");
811        }
812
813        private synchronized void ignoreRing() {
814            mCallsetup = 0;
815            mIgnoreRing = true;
816            if (sendUpdate()) {
817                sendURC("+CIEV: 3," + mCallsetup);
818            }
819        }
820
821    };
822
823    private static final int SCO_ACCEPTED = 1;
824    private static final int SCO_CONNECTED = 2;
825    private static final int SCO_CLOSED = 3;
826    private static final int CHECK_CALL_STARTED = 4;
827    private static final int CHECK_VOICE_RECOGNITION_STARTED = 5;
828
829    private final Handler mHandler = new Handler() {
830        @Override
831        public void handleMessage(Message msg) {
832            synchronized (BluetoothHandsfree.this) {
833                switch (msg.what) {
834                case SCO_ACCEPTED:
835                    if (msg.arg1 == ScoSocket.STATE_CONNECTED) {
836                        if (isHeadsetConnected() && (mAudioPossible || allowAudioAnytime()) &&
837                                mConnectedSco == null) {
838                            Log.i(TAG, "Routing audio for incoming SCO connection");
839                            mConnectedSco = (ScoSocket)msg.obj;
840                            mAudioManager.setBluetoothScoOn(true);
841                            broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_CONNECTED, mHeadset.getAddress());
842                        } else {
843                            Log.i(TAG, "Rejecting incoming SCO connection");
844                            ((ScoSocket)msg.obj).close();
845                        }
846                    } // else error trying to accept, try again
847                    mIncomingSco = createScoSocket();
848                    mIncomingSco.accept();
849                    break;
850                case SCO_CONNECTED:
851                    if (msg.arg1 == ScoSocket.STATE_CONNECTED && isHeadsetConnected() &&
852                            mConnectedSco == null) {
853                        if (DBG) log("Routing audio for outgoing SCO conection");
854                        mConnectedSco = (ScoSocket)msg.obj;
855                        mAudioManager.setBluetoothScoOn(true);
856                        broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_CONNECTED, mHeadset.getAddress());
857                    } else if (msg.arg1 == ScoSocket.STATE_CONNECTED) {
858                        if (DBG) log("Rejecting new connected outgoing SCO socket");
859                        ((ScoSocket)msg.obj).close();
860                        mOutgoingSco.close();
861                    }
862                    mOutgoingSco = null;
863                    break;
864                case SCO_CLOSED:
865                    if (mConnectedSco == (ScoSocket)msg.obj) {
866                        mConnectedSco = null;
867                        mAudioManager.setBluetoothScoOn(false);
868                        broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_DISCONNECTED, mHeadset.getAddress());
869                    } else if (mOutgoingSco == (ScoSocket)msg.obj) {
870                        mOutgoingSco = null;
871                    } else if (mIncomingSco == (ScoSocket)msg.obj) {
872                        mIncomingSco = null;
873                    }
874                    break;
875                case CHECK_CALL_STARTED:
876                    if (mWaitingForCallStart) {
877                        mWaitingForCallStart = false;
878                        Log.e(TAG, "Timeout waiting for call to start");
879                        sendURC("ERROR");
880                        if (mStartCallWakeLock.isHeld()) {
881                            mStartCallWakeLock.release();
882                        }
883                    }
884                    break;
885                case CHECK_VOICE_RECOGNITION_STARTED:
886                    if (mWaitingForVoiceRecognition) {
887                        mWaitingForVoiceRecognition = false;
888                        Log.e(TAG, "Timeout waiting for voice recognition to start");
889                        sendURC("ERROR");
890                    }
891                    break;
892                }
893            }
894        }
895    };
896
897    private ScoSocket createScoSocket() {
898        return new ScoSocket(mPowerManager, mHandler, SCO_ACCEPTED, SCO_CONNECTED, SCO_CLOSED);
899    }
900
901    private void broadcastAudioStateIntent(int state, String address) {
902        if (VDBG) log("broadcastAudioStateIntent(" + state + ")");
903        Intent intent = new Intent(BluetoothIntent.HEADSET_AUDIO_STATE_CHANGED_ACTION);
904        intent.putExtra(BluetoothIntent.HEADSET_AUDIO_STATE, state);
905        intent.putExtra(BluetoothIntent.ADDRESS, address);
906        mContext.sendBroadcast(intent, android.Manifest.permission.BLUETOOTH);
907    }
908
909
910    void updateBtHandsfreeAfterRadioTechnologyChange() {
911        if(DBG) Log.d(TAG, "updateBtHandsfreeAfterRadioTechnologyChange...");
912
913        //Get the Call references from the new active phone again
914        mRingingCall = mPhone.getRingingCall();
915        mForegroundCall = mPhone.getForegroundCall();
916        mBackgroundCall = mPhone.getBackgroundCall();
917
918        mBluetoothPhoneState.updateBtPhoneStateAfterRadioTechnologyChange();
919    }
920
921    /** Request to establish SCO (audio) connection to bluetooth
922     * headset/handsfree, if one is connected. Does not block.
923     * Returns false if the user has requested audio off, or if there
924     * is some other immediate problem that will prevent BT audio.
925     */
926    /* package */ synchronized boolean audioOn() {
927        if (VDBG) log("audioOn()");
928        if (!isHeadsetConnected()) {
929            if (DBG) log("audioOn(): headset is not connected!");
930            return false;
931        }
932        if (mHeadsetType == TYPE_HANDSFREE && !mServiceConnectionEstablished) {
933            if (DBG) log("audioOn(): service connection not yet established!");
934            return false;
935        }
936
937        if (mConnectedSco != null) {
938            if (DBG) log("audioOn(): audio is already connected");
939            return true;
940        }
941
942        if (!mUserWantsAudio) {
943            if (DBG) log("audioOn(): user requested no audio, ignoring");
944            return false;
945        }
946
947        if (mOutgoingSco != null) {
948            if (DBG) log("audioOn(): outgoing SCO already in progress");
949            return true;
950        }
951        mOutgoingSco = createScoSocket();
952        if (!mOutgoingSco.connect(mHeadset.getAddress())) {
953            mOutgoingSco = null;
954        }
955
956        return true;
957    }
958
959    /** Used to indicate the user requested BT audio on.
960     *  This will establish SCO (BT audio), even if the user requested it off
961     *  previously on this call.
962     */
963    /* package */ synchronized void userWantsAudioOn() {
964        mUserWantsAudio = true;
965        audioOn();
966    }
967    /** Used to indicate the user requested BT audio off.
968     *  This will prevent us from establishing BT audio again during this call
969     *  if audioOn() is called.
970     */
971    /* package */ synchronized void userWantsAudioOff() {
972        mUserWantsAudio = false;
973        audioOff();
974    }
975
976    /** Request to disconnect SCO (audio) connection to bluetooth
977     * headset/handsfree, if one is connected. Does not block.
978     */
979    /* package */ synchronized void audioOff() {
980        if (VDBG) log("audioOff()");
981
982        if (mConnectedSco != null) {
983            mAudioManager.setBluetoothScoOn(false);
984            broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_DISCONNECTED, mHeadset.getAddress());
985            mConnectedSco.close();
986            mConnectedSco = null;
987        }
988        if (mOutgoingSco != null) {
989            mOutgoingSco.close();
990            mOutgoingSco = null;
991        }
992    }
993
994    /* package */ boolean isAudioOn() {
995        return (mConnectedSco != null);
996    }
997
998    /* package */ void ignoreRing() {
999        mBluetoothPhoneState.ignoreRing();
1000    }
1001
1002    private void sendURC(String urc) {
1003        if (isHeadsetConnected()) {
1004            mHeadset.sendURC(urc);
1005        }
1006    }
1007
1008    /** helper to redial last dialled number */
1009    private AtCommandResult redial() {
1010        String number = mPhonebook.getLastDialledNumber();
1011        if (number == null) {
1012            // spec seems to suggest sending ERROR if we dont have a
1013            // number to redial
1014            if (DBG) log("Bluetooth redial requested (+BLDN), but no previous " +
1015                  "outgoing calls found. Ignoring");
1016            return new AtCommandResult(AtCommandResult.ERROR);
1017        }
1018        Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1019                Uri.fromParts("tel", number, null));
1020        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1021        mContext.startActivity(intent);
1022
1023        // We do not immediately respond OK, wait until we get a phone state
1024        // update. If we return OK now and the handsfree immeidately requests
1025        // our phone state it will say we are not in call yet which confuses
1026        // some devices
1027        expectCallStart();
1028        return new AtCommandResult(AtCommandResult.UNSOLICITED);  // send nothing
1029    }
1030
1031    /** Build the +CLCC result
1032     *  The complexity arises from the fact that we need to maintain the same
1033     *  CLCC index even as a call moves between states. */
1034    private synchronized AtCommandResult gsmGetClccResult() {
1035        // Collect all known connections
1036        Connection[] clccConnections = new Connection[GSM_MAX_CONNECTIONS];  // indexed by CLCC index
1037        LinkedList<Connection> newConnections = new LinkedList<Connection>();
1038        LinkedList<Connection> connections = new LinkedList<Connection>();
1039        if (mRingingCall.getState().isAlive()) {
1040            connections.addAll(mRingingCall.getConnections());
1041        }
1042        if (mForegroundCall.getState().isAlive()) {
1043            connections.addAll(mForegroundCall.getConnections());
1044        }
1045        if (mBackgroundCall.getState().isAlive()) {
1046            connections.addAll(mBackgroundCall.getConnections());
1047        }
1048
1049        // Mark connections that we already known about
1050        boolean clccUsed[] = new boolean[GSM_MAX_CONNECTIONS];
1051        for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
1052            clccUsed[i] = mClccUsed[i];
1053            mClccUsed[i] = false;
1054        }
1055        for (Connection c : connections) {
1056            boolean found = false;
1057            long timestamp = c.getCreateTime();
1058            for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
1059                if (clccUsed[i] && timestamp == mClccTimestamps[i]) {
1060                    mClccUsed[i] = true;
1061                    found = true;
1062                    clccConnections[i] = c;
1063                    break;
1064                }
1065            }
1066            if (!found) {
1067                newConnections.add(c);
1068            }
1069        }
1070
1071        // Find a CLCC index for new connections
1072        while (!newConnections.isEmpty()) {
1073            // Find lowest empty index
1074            int i = 0;
1075            while (mClccUsed[i]) i++;
1076            // Find earliest connection
1077            long earliestTimestamp = newConnections.get(0).getCreateTime();
1078            Connection earliestConnection = newConnections.get(0);
1079            for (int j = 0; j < newConnections.size(); j++) {
1080                long timestamp = newConnections.get(j).getCreateTime();
1081                if (timestamp < earliestTimestamp) {
1082                    earliestTimestamp = timestamp;
1083                    earliestConnection = newConnections.get(j);
1084                }
1085            }
1086
1087            // update
1088            mClccUsed[i] = true;
1089            mClccTimestamps[i] = earliestTimestamp;
1090            clccConnections[i] = earliestConnection;
1091            newConnections.remove(earliestConnection);
1092        }
1093
1094        // Build CLCC
1095        AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
1096        for (int i = 0; i < clccConnections.length; i++) {
1097            if (mClccUsed[i]) {
1098                String clccEntry = connectionToClccEntry(i, clccConnections[i]);
1099                if (clccEntry != null) {
1100                    result.addResponse(clccEntry);
1101                }
1102            }
1103        }
1104
1105        return result;
1106    }
1107
1108    /** Convert a Connection object into a single +CLCC result */
1109    private String connectionToClccEntry(int index, Connection c) {
1110        int state;
1111        switch (c.getState()) {
1112        case ACTIVE:
1113            state = 0;
1114            break;
1115        case HOLDING:
1116            state = 1;
1117            break;
1118        case DIALING:
1119            state = 2;
1120            break;
1121        case ALERTING:
1122            state = 3;
1123            break;
1124        case INCOMING:
1125            state = 4;
1126            break;
1127        case WAITING:
1128            state = 5;
1129            break;
1130        default:
1131            return null;  // bad state
1132        }
1133
1134        int mpty = 0;
1135        Call call = c.getCall();
1136        if (call != null) {
1137            mpty = call.isMultiparty() ? 1 : 0;
1138        }
1139
1140        int direction = c.isIncoming() ? 1 : 0;
1141
1142        String number = c.getAddress();
1143        int type = -1;
1144        if (number != null) {
1145            type = PhoneNumberUtils.toaFromString(number);
1146        }
1147
1148        String result = "+CLCC: " + (index + 1) + "," + direction + "," + state + ",0," + mpty;
1149        if (number != null) {
1150            result += ",\"" + number + "\"," + type;
1151        }
1152        return result;
1153    }
1154
1155    /** Build the +CLCC result for CDMA
1156     *  The complexity arises from the fact that we need to maintain the same
1157     *  CLCC index even as a call moves between states. */
1158    private synchronized AtCommandResult cdmaGetClccResult() {
1159        // In CDMA at one time a user can have only two live/active connections
1160        Connection[] clccConnections = new Connection[CDMA_MAX_CONNECTIONS];// indexed by CLCC index
1161
1162        Call.State ringingCallState = mRingingCall.getState();
1163        // If the Ringing Call state is INCOMING, that means this is the very first call
1164        // hence there should not be any Foreground Call
1165        if (ringingCallState == Call.State.INCOMING) {
1166            if (DBG) log("Filling clccConnections[0] for INCOMING state");
1167            clccConnections[0] = mRingingCall.getLatestConnection();
1168        } else if (mForegroundCall.getState().isAlive()) {
1169            // Getting Foreground Call connection based on Call state
1170            if (mRingingCall.isRinging()) {
1171                if (DBG) log("Filling clccConnections[0] & [1] for CALL WAITING state");
1172                clccConnections[0] = mForegroundCall.getEarliestConnection();
1173                clccConnections[1] = mRingingCall.getLatestConnection();
1174            } else {
1175                if (mForegroundCall.getConnections().size() <= 1) {
1176                    // Single call scenario
1177                    if (DBG) log("Filling clccConnections[0] with ForgroundCall latest connection");
1178                    clccConnections[0] = mForegroundCall.getLatestConnection();
1179                } else {
1180                    // Multiple Call scenario. This would be true for both
1181                    // CONF_CALL and THRWAY_ACTIVE state
1182                    if (DBG) log("Filling clccConnections[0] & [1] with ForgroundCall connections");
1183                    clccConnections[0] = mForegroundCall.getEarliestConnection();
1184                    clccConnections[1] = mForegroundCall.getLatestConnection();
1185                }
1186            }
1187        }
1188
1189        // Update the mCdmaIsSecondCallActive flag based on the Phone call state
1190        if (PhoneApp.getInstance().cdmaPhoneCallState.getCurrentCallState()
1191                == CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE) {
1192            cdmaSetSecondCallState(false);
1193        } else if (PhoneApp.getInstance().cdmaPhoneCallState.getCurrentCallState()
1194                == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
1195            cdmaSetSecondCallState(true);
1196        }
1197
1198        // Build CLCC
1199        AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
1200        for (int i = 0; (i < clccConnections.length) && (clccConnections[i] != null); i++) {
1201            String clccEntry = cdmaConnectionToClccEntry(i, clccConnections[i]);
1202            if (clccEntry != null) {
1203                result.addResponse(clccEntry);
1204            }
1205        }
1206
1207        return result;
1208    }
1209
1210    /** Convert a Connection object into a single +CLCC result for CDMA phones */
1211    private String cdmaConnectionToClccEntry(int index, Connection c) {
1212        int state;
1213        PhoneApp app = PhoneApp.getInstance();
1214        CdmaPhoneCallState.PhoneCallState currCdmaCallState =
1215                app.cdmaPhoneCallState.getCurrentCallState();
1216        CdmaPhoneCallState.PhoneCallState prevCdmaCallState =
1217                app.cdmaPhoneCallState.getPreviousCallState();
1218
1219        if ((prevCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
1220                && (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL)) {
1221            // If the current state is reached after merging two calls
1222            // we set the state of all the connections as ACTIVE
1223            state = 0;
1224        } else {
1225            switch (c.getState()) {
1226            case ACTIVE:
1227                // For CDMA since both the connections are set as active by FW after accepting
1228                // a Call waiting or making a 3 way call, we need to set the state specifically
1229                // to ACTIVE/HOLDING based on the mCdmaIsSecondCallActive flag. This way the
1230                // CLCC result will allow BT devices to enable the swap or merge options
1231                if (index == 0) { // For the 1st active connection
1232                    state = mCdmaIsSecondCallActive ? 1 : 0;
1233                } else { // for the 2nd active connection
1234                    state = mCdmaIsSecondCallActive ? 0 : 1;
1235                }
1236                break;
1237            case HOLDING:
1238                state = 1;
1239                break;
1240            case DIALING:
1241                state = 2;
1242                break;
1243            case ALERTING:
1244                state = 3;
1245                break;
1246            case INCOMING:
1247                state = 4;
1248                break;
1249            case WAITING:
1250                state = 5;
1251                break;
1252            default:
1253                return null;  // bad state
1254            }
1255        }
1256
1257        int mpty = 0;
1258        if (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE) {
1259            mpty = 0;
1260        } else {
1261            mpty = 1;
1262        }
1263
1264        int direction = c.isIncoming() ? 1 : 0;
1265
1266        String number = c.getAddress();
1267        int type = -1;
1268        if (number != null) {
1269            type = PhoneNumberUtils.toaFromString(number);
1270        }
1271
1272        String result = "+CLCC: " + (index + 1) + "," + direction + "," + state + ",0," + mpty;
1273        if (number != null) {
1274            result += ",\"" + number + "\"," + type;
1275        }
1276        return result;
1277    }
1278
1279    /**
1280     * Register AT Command handlers to implement the Headset profile
1281     */
1282    private void initializeHeadsetAtParser() {
1283        if (DBG) log("Registering Headset AT commands");
1284        AtParser parser = mHeadset.getAtParser();
1285        // Headset's usually only have one button, which is meant to cause the
1286        // HS to send us AT+CKPD=200 or AT+CKPD.
1287        parser.register("+CKPD", new AtCommandHandler() {
1288            private AtCommandResult headsetButtonPress() {
1289                if (mRingingCall.isRinging()) {
1290                    // Answer the call
1291                    PhoneUtils.answerCall(mPhone);
1292                    // If in-band ring tone is supported, SCO connection will already
1293                    // be up and the following call will just return.
1294                    audioOn();
1295                } else if (mForegroundCall.getState().isAlive()) {
1296                    if (!isAudioOn()) {
1297                        // Transfer audio from AG to HS
1298                        audioOn();
1299                    } else {
1300                        if (mHeadset.getDirection() == HeadsetBase.DIRECTION_INCOMING &&
1301                          (System.currentTimeMillis() - mHeadset.getConnectTimestamp()) < 5000) {
1302                            // Headset made a recent ACL connection to us - and
1303                            // made a mandatory AT+CKPD request to connect
1304                            // audio which races with our automatic audio
1305                            // setup.  ignore
1306                        } else {
1307                            // Hang up the call
1308                            audioOff();
1309                            PhoneUtils.hangup(mPhone);
1310                        }
1311                    }
1312                } else {
1313                    // No current call - redial last number
1314                    return redial();
1315                }
1316                return new AtCommandResult(AtCommandResult.OK);
1317            }
1318            @Override
1319            public AtCommandResult handleActionCommand() {
1320                return headsetButtonPress();
1321            }
1322            @Override
1323            public AtCommandResult handleSetCommand(Object[] args) {
1324                return headsetButtonPress();
1325            }
1326        });
1327    }
1328
1329    /**
1330     * Register AT Command handlers to implement the Handsfree profile
1331     */
1332    private void initializeHandsfreeAtParser() {
1333        if (DBG) log("Registering Handsfree AT commands");
1334        AtParser parser = mHeadset.getAtParser();
1335
1336        // Answer
1337        parser.register('A', new AtCommandHandler() {
1338            @Override
1339            public AtCommandResult handleBasicCommand(String args) {
1340                PhoneUtils.answerCall(mPhone);
1341                return new AtCommandResult(AtCommandResult.OK);
1342            }
1343        });
1344        parser.register('D', new AtCommandHandler() {
1345            @Override
1346            public AtCommandResult handleBasicCommand(String args) {
1347                if (args.length() > 0) {
1348                    if (args.charAt(0) == '>') {
1349                        // Yuck - memory dialling requested.
1350                        // Just dial last number for now
1351                        if (args.startsWith(">9999")) {   // for PTS test
1352                            return new AtCommandResult(AtCommandResult.ERROR);
1353                        }
1354                        return redial();
1355                    } else {
1356                        // Remove trailing ';'
1357                        if (args.charAt(args.length() - 1) == ';') {
1358                            args = args.substring(0, args.length() - 1);
1359                        }
1360                        Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1361                                Uri.fromParts("tel", args, null));
1362                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1363                        mContext.startActivity(intent);
1364
1365                        expectCallStart();
1366                        return new AtCommandResult(AtCommandResult.UNSOLICITED);  // send nothing
1367                    }
1368                }
1369                return new AtCommandResult(AtCommandResult.ERROR);
1370            }
1371        });
1372
1373        // Hang-up command
1374        parser.register("+CHUP", new AtCommandHandler() {
1375            @Override
1376            public AtCommandResult handleActionCommand() {
1377                if (!mRingingCall.isIdle()) {
1378                    PhoneUtils.hangupRingingCall(mPhone);
1379                } else if (!mForegroundCall.isIdle()) {
1380                    PhoneUtils.hangupActiveCall(mPhone);
1381                } else if (!mBackgroundCall.isIdle()) {
1382                    PhoneUtils.hangupHoldingCall(mPhone);
1383                }
1384                return new AtCommandResult(AtCommandResult.OK);
1385            }
1386        });
1387
1388        // Bluetooth Retrieve Supported Features command
1389        parser.register("+BRSF", new AtCommandHandler() {
1390            private AtCommandResult sendBRSF() {
1391                return new AtCommandResult("+BRSF: " + mLocalBrsf);
1392            }
1393            @Override
1394            public AtCommandResult handleSetCommand(Object[] args) {
1395                // AT+BRSF=<handsfree supported features bitmap>
1396                // Handsfree is telling us which features it supports. We
1397                // send the features we support
1398                if (args.length == 1 && (args[0] instanceof Integer)) {
1399                    mRemoteBrsf = (Integer) args[0];
1400                } else {
1401                    Log.w(TAG, "HF didn't sent BRSF assuming 0");
1402                }
1403                return sendBRSF();
1404            }
1405            @Override
1406            public AtCommandResult handleActionCommand() {
1407                // This seems to be out of spec, but lets do the nice thing
1408                return sendBRSF();
1409            }
1410            @Override
1411            public AtCommandResult handleReadCommand() {
1412                // This seems to be out of spec, but lets do the nice thing
1413                return sendBRSF();
1414            }
1415        });
1416
1417        // Call waiting notification on/off
1418        parser.register("+CCWA", new AtCommandHandler() {
1419            @Override
1420            public AtCommandResult handleActionCommand() {
1421                // Seems to be out of spec, but lets return nicely
1422                return new AtCommandResult(AtCommandResult.OK);
1423            }
1424            @Override
1425            public AtCommandResult handleReadCommand() {
1426                // Call waiting is always on
1427                return new AtCommandResult("+CCWA: 1");
1428            }
1429            @Override
1430            public AtCommandResult handleSetCommand(Object[] args) {
1431                // AT+CCWA=<n>
1432                // Handsfree is trying to enable/disable call waiting. We
1433                // cannot disable in the current implementation.
1434                return new AtCommandResult(AtCommandResult.OK);
1435            }
1436            @Override
1437            public AtCommandResult handleTestCommand() {
1438                // Request for range of supported CCWA paramters
1439                return new AtCommandResult("+CCWA: (\"n\",(1))");
1440            }
1441        });
1442
1443        // Mobile Equipment Event Reporting enable/disable command
1444        // Of the full 3GPP syntax paramters (mode, keyp, disp, ind, bfr) we
1445        // only support paramter ind (disable/enable evert reporting using
1446        // +CDEV)
1447        parser.register("+CMER", new AtCommandHandler() {
1448            @Override
1449            public AtCommandResult handleReadCommand() {
1450                return new AtCommandResult(
1451                        "+CMER: 3,0,0," + (mIndicatorsEnabled ? "1" : "0"));
1452            }
1453            @Override
1454            public AtCommandResult handleSetCommand(Object[] args) {
1455                if (args.length < 4) {
1456                    // This is a syntax error
1457                    return new AtCommandResult(AtCommandResult.ERROR);
1458                } else if (args[0].equals(3) && args[1].equals(0) &&
1459                           args[2].equals(0)) {
1460                    boolean valid = false;
1461                    if (args[3].equals(0)) {
1462                        mIndicatorsEnabled = false;
1463                        valid = true;
1464                    } else if (args[3].equals(1)) {
1465                        mIndicatorsEnabled = true;
1466                        valid = true;
1467                    }
1468                    if (valid) {
1469                        if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) == 0x0) {
1470                            mServiceConnectionEstablished = true;
1471                            sendURC("OK");  // send immediately, then initiate audio
1472                            if (isIncallAudio()) {
1473                                audioOn();
1474                            }
1475                            // only send OK once
1476                            return new AtCommandResult(AtCommandResult.UNSOLICITED);
1477                        } else {
1478                            return new AtCommandResult(AtCommandResult.OK);
1479                        }
1480                    }
1481                }
1482                return reportCmeError(BluetoothCmeError.OPERATION_NOT_SUPPORTED);
1483            }
1484            @Override
1485            public AtCommandResult handleTestCommand() {
1486                return new AtCommandResult("+CMER: (3),(0),(0),(0-1)");
1487            }
1488        });
1489
1490        // Mobile Equipment Error Reporting enable/disable
1491        parser.register("+CMEE", new AtCommandHandler() {
1492            @Override
1493            public AtCommandResult handleActionCommand() {
1494                // out of spec, assume they want to enable
1495                mCmee = true;
1496                return new AtCommandResult(AtCommandResult.OK);
1497            }
1498            @Override
1499            public AtCommandResult handleReadCommand() {
1500                return new AtCommandResult("+CMEE: " + (mCmee ? "1" : "0"));
1501            }
1502            @Override
1503            public AtCommandResult handleSetCommand(Object[] args) {
1504                // AT+CMEE=<n>
1505                if (args.length == 0) {
1506                    // <n> ommitted - default to 0
1507                    mCmee = false;
1508                    return new AtCommandResult(AtCommandResult.OK);
1509                } else if (!(args[0] instanceof Integer)) {
1510                    // Syntax error
1511                    return new AtCommandResult(AtCommandResult.ERROR);
1512                } else {
1513                    mCmee = ((Integer)args[0] == 1);
1514                    return new AtCommandResult(AtCommandResult.OK);
1515                }
1516            }
1517            @Override
1518            public AtCommandResult handleTestCommand() {
1519                // Probably not required but spec, but no harm done
1520                return new AtCommandResult("+CMEE: (0-1)");
1521            }
1522        });
1523
1524        // Bluetooth Last Dialled Number
1525        parser.register("+BLDN", new AtCommandHandler() {
1526            @Override
1527            public AtCommandResult handleActionCommand() {
1528                return redial();
1529            }
1530        });
1531
1532        // Indicator Update command
1533        parser.register("+CIND", new AtCommandHandler() {
1534            @Override
1535            public AtCommandResult handleReadCommand() {
1536                return mBluetoothPhoneState.toCindResult();
1537            }
1538            @Override
1539            public AtCommandResult handleTestCommand() {
1540                return mBluetoothPhoneState.getCindTestResult();
1541            }
1542        });
1543
1544        // Query Signal Quality (legacy)
1545        parser.register("+CSQ", new AtCommandHandler() {
1546            @Override
1547            public AtCommandResult handleActionCommand() {
1548                return mBluetoothPhoneState.toCsqResult();
1549            }
1550        });
1551
1552        // Query network registration state
1553        parser.register("+CREG", new AtCommandHandler() {
1554            @Override
1555            public AtCommandResult handleReadCommand() {
1556                return new AtCommandResult(mBluetoothPhoneState.toCregString());
1557            }
1558        });
1559
1560        // Send DTMF. I don't know if we are also expected to play the DTMF tone
1561        // locally, right now we don't
1562        parser.register("+VTS", new AtCommandHandler() {
1563            @Override
1564            public AtCommandResult handleSetCommand(Object[] args) {
1565                if (args.length >= 1) {
1566                    char c;
1567                    if (args[0] instanceof Integer) {
1568                        c = ((Integer) args[0]).toString().charAt(0);
1569                    } else {
1570                        c = ((String) args[0]).charAt(0);
1571                    }
1572                    if (isValidDtmf(c)) {
1573                        mPhone.sendDtmf(c);
1574                        return new AtCommandResult(AtCommandResult.OK);
1575                    }
1576                }
1577                return new AtCommandResult(AtCommandResult.ERROR);
1578            }
1579            private boolean isValidDtmf(char c) {
1580                switch (c) {
1581                case '#':
1582                case '*':
1583                    return true;
1584                default:
1585                    if (Character.digit(c, 14) != -1) {
1586                        return true;  // 0-9 and A-D
1587                    }
1588                    return false;
1589                }
1590            }
1591        });
1592
1593        // List calls
1594        parser.register("+CLCC", new AtCommandHandler() {
1595            @Override
1596            public AtCommandResult handleActionCommand() {
1597                if (mPhone.getPhoneName().equals("CDMA")) {
1598                    return cdmaGetClccResult();
1599                } else {
1600                    return gsmGetClccResult();
1601                }
1602            }
1603        });
1604
1605        // Call Hold and Multiparty Handling command
1606        parser.register("+CHLD", new AtCommandHandler() {
1607            @Override
1608            public AtCommandResult handleSetCommand(Object[] args) {
1609                if (args.length >= 1) {
1610                    if (args[0].equals(0)) {
1611                        boolean result;
1612                        if (mRingingCall.isRinging()) {
1613                            result = PhoneUtils.hangupRingingCall(mPhone);
1614                        } else {
1615                            result = PhoneUtils.hangupHoldingCall(mPhone);
1616                        }
1617                        if (result) {
1618                            return new AtCommandResult(AtCommandResult.OK);
1619                        } else {
1620                            return new AtCommandResult(AtCommandResult.ERROR);
1621                        }
1622                    } else if (args[0].equals(1)) {
1623                        if (mPhone.getPhoneName().equals("CDMA")) {
1624                            if (mRingingCall.isRinging()) {
1625                                // If there is Call waiting then answer the call and
1626                                // put the first call on hold.
1627                                if (DBG) log("CHLD:1 Callwaiting Answer call");
1628                                PhoneUtils.answerCall(mPhone);
1629                                PhoneUtils.setMute(mPhone, false);
1630                                // Setting the second callers state flag to TRUE (i.e. active)
1631                                cdmaSetSecondCallState(true);
1632                            } else {
1633                                // If there is no Call waiting then just hangup
1634                                // the active call. In CDMA this mean that the complete
1635                                // call session would be ended
1636                                if (DBG) log("CHLD:1 Hangup Call");
1637                                PhoneUtils.hangup(mPhone);
1638                            }
1639                            return new AtCommandResult(AtCommandResult.OK);
1640                        } else { // GSM
1641                            // Hangup active call, answer held call
1642                            if (PhoneUtils.answerAndEndActive(mPhone)) {
1643                                return new AtCommandResult(AtCommandResult.OK);
1644                            } else {
1645                                return new AtCommandResult(AtCommandResult.ERROR);
1646                            }
1647                        }
1648                    } else if (args[0].equals(2)) {
1649                        if (mPhone.getPhoneName().equals("CDMA")) {
1650                            // For CDMA, the way we switch to a new incoming call is by
1651                            // calling PhoneUtils.answerCall(). switchAndHoldActive() won't
1652                            // properly update the call state within telephony.
1653                            // If the Phone state is already in CONF_CALL then we simply send
1654                            // a flash cmd by calling switchHoldingAndActive()
1655                            if (mRingingCall.isRinging()) {
1656                                if (DBG) log("CHLD:2 Callwaiting Answer call");
1657                                PhoneUtils.answerCall(mPhone);
1658                                PhoneUtils.setMute(mPhone, false);
1659                                // Setting the second callers state flag to TRUE (i.e. active)
1660                                cdmaSetSecondCallState(true);
1661                            } else if (PhoneApp.getInstance().cdmaPhoneCallState
1662                                    .getCurrentCallState()
1663                                    == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
1664                                if (DBG) log("CHLD:2 Swap Calls");
1665                                PhoneUtils.switchHoldingAndActive(mPhone);
1666                                // Toggle the second callers active state flag
1667                                cdmaSwapSecondCallState();
1668                            }
1669                        } else { // GSM
1670                            PhoneUtils.switchHoldingAndActive(mPhone);
1671                        }
1672                        return new AtCommandResult(AtCommandResult.OK);
1673                    } else if (args[0].equals(3)) {
1674                        if (mPhone.getPhoneName().equals("CDMA")) {
1675                            // For CDMA, we need to check if the call is in THRWAY_ACTIVE state
1676                            if (PhoneApp.getInstance().cdmaPhoneCallState.getCurrentCallState()
1677                                    == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
1678                                if (DBG) log("CHLD:3 Merge Calls");
1679                                PhoneUtils.mergeCalls(mPhone);
1680                            }
1681                        } else { // GSM
1682                            if (mForegroundCall.getState().isAlive() &&
1683                                    mBackgroundCall.getState().isAlive()) {
1684                                PhoneUtils.mergeCalls(mPhone);
1685                            }
1686                        }
1687                        return new AtCommandResult(AtCommandResult.OK);
1688                    }
1689                }
1690                return new AtCommandResult(AtCommandResult.ERROR);
1691            }
1692            @Override
1693            public AtCommandResult handleTestCommand() {
1694                mServiceConnectionEstablished = true;
1695                sendURC("+CHLD: (0,1,2,3)");
1696                sendURC("OK");  // send reply first, then connect audio
1697                if (isIncallAudio()) {
1698                    audioOn();
1699                }
1700                // already replied
1701                return new AtCommandResult(AtCommandResult.UNSOLICITED);
1702            }
1703        });
1704
1705        // Get Network operator name
1706        parser.register("+COPS", new AtCommandHandler() {
1707            @Override
1708            public AtCommandResult handleReadCommand() {
1709                String operatorName = mPhone.getServiceState().getOperatorAlphaLong();
1710                if (operatorName != null) {
1711                    if (operatorName.length() > 16) {
1712                        operatorName = operatorName.substring(0, 16);
1713                    }
1714                    return new AtCommandResult(
1715                            "+COPS: 0,0,\"" + operatorName + "\"");
1716                } else {
1717                    return new AtCommandResult(
1718                            "+COPS: 0,0,\"UNKNOWN\",0");
1719                }
1720            }
1721            @Override
1722            public AtCommandResult handleSetCommand(Object[] args) {
1723                // Handsfree only supports AT+COPS=3,0
1724                if (args.length != 2 || !(args[0] instanceof Integer)
1725                    || !(args[1] instanceof Integer)) {
1726                    // syntax error
1727                    return new AtCommandResult(AtCommandResult.ERROR);
1728                } else if ((Integer)args[0] != 3 || (Integer)args[1] != 0) {
1729                    return reportCmeError(BluetoothCmeError.OPERATION_NOT_SUPPORTED);
1730                } else {
1731                    return new AtCommandResult(AtCommandResult.OK);
1732                }
1733            }
1734            @Override
1735            public AtCommandResult handleTestCommand() {
1736                // Out of spec, but lets be friendly
1737                return new AtCommandResult("+COPS: (3),(0)");
1738            }
1739        });
1740
1741        // Mobile PIN
1742        // AT+CPIN is not in the handsfree spec (although it is in 3GPP)
1743        parser.register("+CPIN", new AtCommandHandler() {
1744            @Override
1745            public AtCommandResult handleReadCommand() {
1746                return new AtCommandResult("+CPIN: READY");
1747            }
1748        });
1749
1750        // Bluetooth Response and Hold
1751        // Only supported on PDC (Japan) and CDMA networks.
1752        parser.register("+BTRH", new AtCommandHandler() {
1753            @Override
1754            public AtCommandResult handleReadCommand() {
1755                // Replying with just OK indicates no response and hold
1756                // features in use now
1757                return new AtCommandResult(AtCommandResult.OK);
1758            }
1759            @Override
1760            public AtCommandResult handleSetCommand(Object[] args) {
1761                // Neeed PDC or CDMA
1762                return new AtCommandResult(AtCommandResult.ERROR);
1763            }
1764        });
1765
1766        // Request International Mobile Subscriber Identity (IMSI)
1767        // Not in bluetooth handset spec
1768        parser.register("+CIMI", new AtCommandHandler() {
1769            @Override
1770            public AtCommandResult handleActionCommand() {
1771                // AT+CIMI
1772                String imsi = mPhone.getSubscriberId();
1773                if (imsi == null || imsi.length() == 0) {
1774                    return reportCmeError(BluetoothCmeError.SIM_FAILURE);
1775                } else {
1776                    return new AtCommandResult(imsi);
1777                }
1778            }
1779        });
1780
1781        // Calling Line Identification Presentation
1782        parser.register("+CLIP", new AtCommandHandler() {
1783            @Override
1784            public AtCommandResult handleReadCommand() {
1785                // Currently assumes the network is provisioned for CLIP
1786                return new AtCommandResult("+CLIP: " + (mClip ? "1" : "0") + ",1");
1787            }
1788            @Override
1789            public AtCommandResult handleSetCommand(Object[] args) {
1790                // AT+CLIP=<n>
1791                if (args.length >= 1 && (args[0].equals(0) || args[0].equals(1))) {
1792                    mClip = args[0].equals(1);
1793                    return new AtCommandResult(AtCommandResult.OK);
1794                } else {
1795                    return new AtCommandResult(AtCommandResult.ERROR);
1796                }
1797            }
1798            @Override
1799            public AtCommandResult handleTestCommand() {
1800                return new AtCommandResult("+CLIP: (0-1)");
1801            }
1802        });
1803
1804        // AT+CGSN - Returns the device IMEI number.
1805        parser.register("+CGSN", new AtCommandHandler() {
1806            @Override
1807            public AtCommandResult handleActionCommand() {
1808                // Get the IMEI of the device.
1809                // mPhone will not be NULL at this point.
1810                return new AtCommandResult("+CGSN: " + mPhone.getDeviceId());
1811            }
1812        });
1813
1814        // AT+CGMM - Query Model Information
1815        parser.register("+CGMM", new AtCommandHandler() {
1816            @Override
1817            public AtCommandResult handleActionCommand() {
1818                // Return the Model Information.
1819                String model = SystemProperties.get("ro.product.model");
1820                if (model != null) {
1821                    return new AtCommandResult("+CGMM: " + model);
1822                } else {
1823                    return new AtCommandResult(AtCommandResult.ERROR);
1824                }
1825            }
1826        });
1827
1828        // AT+CGMI - Query Manufacturer Information
1829        parser.register("+CGMI", new AtCommandHandler() {
1830            @Override
1831            public AtCommandResult handleActionCommand() {
1832                // Return the Model Information.
1833                String manuf = SystemProperties.get("ro.product.manufacturer");
1834                if (manuf != null) {
1835                    return new AtCommandResult("+CGMI: " + manuf);
1836                } else {
1837                    return new AtCommandResult(AtCommandResult.ERROR);
1838                }
1839            }
1840        });
1841
1842        // Noise Reduction and Echo Cancellation control
1843        parser.register("+NREC", new AtCommandHandler() {
1844            @Override
1845            public AtCommandResult handleSetCommand(Object[] args) {
1846                if (args[0].equals(0)) {
1847                    mAudioManager.setParameters(HEADSET_NREC+"=off");
1848                    return new AtCommandResult(AtCommandResult.OK);
1849                } else if (args[0].equals(1)) {
1850                    mAudioManager.setParameters(HEADSET_NREC+"=on");
1851                    return new AtCommandResult(AtCommandResult.OK);
1852                }
1853                return new AtCommandResult(AtCommandResult.ERROR);
1854            }
1855        });
1856
1857        // Voice recognition (dialing)
1858        parser.register("+BVRA", new AtCommandHandler() {
1859            @Override
1860            public AtCommandResult handleSetCommand(Object[] args) {
1861                if (BluetoothHeadset.DISABLE_BT_VOICE_DIALING) {
1862                    return new AtCommandResult(AtCommandResult.ERROR);
1863                }
1864                if (args.length >= 1 && args[0].equals(1)) {
1865                    synchronized (BluetoothHandsfree.this) {
1866                        if (!mWaitingForVoiceRecognition) {
1867                            try {
1868                                mContext.startActivity(sVoiceCommandIntent);
1869                            } catch (ActivityNotFoundException e) {
1870                                return new AtCommandResult(AtCommandResult.ERROR);
1871                            }
1872                            expectVoiceRecognition();
1873                        }
1874                    }
1875                    return new AtCommandResult(AtCommandResult.UNSOLICITED);  // send nothing yet
1876                } else if (args.length >= 1 && args[0].equals(0)) {
1877                    audioOff();
1878                    return new AtCommandResult(AtCommandResult.OK);
1879                }
1880                return new AtCommandResult(AtCommandResult.ERROR);
1881            }
1882            @Override
1883            public AtCommandResult handleTestCommand() {
1884                return new AtCommandResult("+BVRA: (0-1)");
1885            }
1886        });
1887
1888        // Retrieve Subscriber Number
1889        parser.register("+CNUM", new AtCommandHandler() {
1890            @Override
1891            public AtCommandResult handleActionCommand() {
1892                String number = mPhone.getLine1Number();
1893                if (number == null) {
1894                    return new AtCommandResult(AtCommandResult.OK);
1895                }
1896                return new AtCommandResult("+CNUM: ,\"" + number + "\"," +
1897                        PhoneNumberUtils.toaFromString(number) + ",,4");
1898            }
1899        });
1900
1901        // Microphone Gain
1902        parser.register("+VGM", new AtCommandHandler() {
1903            @Override
1904            public AtCommandResult handleSetCommand(Object[] args) {
1905                // AT+VGM=<gain>    in range [0,15]
1906                // Headset/Handsfree is reporting its current gain setting
1907                return new AtCommandResult(AtCommandResult.OK);
1908            }
1909        });
1910
1911        // Speaker Gain
1912        parser.register("+VGS", new AtCommandHandler() {
1913            @Override
1914            public AtCommandResult handleSetCommand(Object[] args) {
1915                // AT+VGS=<gain>    in range [0,15]
1916                if (args.length != 1 || !(args[0] instanceof Integer)) {
1917                    return new AtCommandResult(AtCommandResult.ERROR);
1918                }
1919                mScoGain = (Integer) args[0];
1920                int flag =  mAudioManager.isBluetoothScoOn() ? AudioManager.FLAG_SHOW_UI:0;
1921
1922                mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, mScoGain, flag);
1923                return new AtCommandResult(AtCommandResult.OK);
1924            }
1925        });
1926
1927        // Phone activity status
1928        parser.register("+CPAS", new AtCommandHandler() {
1929            @Override
1930            public AtCommandResult handleActionCommand() {
1931                int status = 0;
1932                switch (mPhone.getState()) {
1933                case IDLE:
1934                    status = 0;
1935                    break;
1936                case RINGING:
1937                    status = 3;
1938                    break;
1939                case OFFHOOK:
1940                    status = 4;
1941                    break;
1942                }
1943                return new AtCommandResult("+CPAS: " + status);
1944            }
1945        });
1946        mPhonebook.register(parser);
1947    }
1948
1949    public void sendScoGainUpdate(int gain) {
1950        if (mScoGain != gain && (mRemoteBrsf & BRSF_HF_REMOTE_VOL_CONTROL) != 0x0) {
1951            sendURC("+VGS:" + gain);
1952            mScoGain = gain;
1953        }
1954    }
1955
1956    public AtCommandResult reportCmeError(int error) {
1957        if (mCmee) {
1958            AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
1959            result.addResponse("+CME ERROR: " + error);
1960            return result;
1961        } else {
1962            return new AtCommandResult(AtCommandResult.ERROR);
1963        }
1964    }
1965
1966    private static final int START_CALL_TIMEOUT = 10000;  // ms
1967
1968    private synchronized void expectCallStart() {
1969        mWaitingForCallStart = true;
1970        Message msg = Message.obtain(mHandler, CHECK_CALL_STARTED);
1971        mHandler.sendMessageDelayed(msg, START_CALL_TIMEOUT);
1972        if (!mStartCallWakeLock.isHeld()) {
1973            mStartCallWakeLock.acquire(START_CALL_TIMEOUT);
1974        }
1975    }
1976
1977    private synchronized void callStarted() {
1978        if (mWaitingForCallStart) {
1979            mWaitingForCallStart = false;
1980            sendURC("OK");
1981            if (mStartCallWakeLock.isHeld()) {
1982                mStartCallWakeLock.release();
1983            }
1984        }
1985    }
1986
1987    private static final int START_VOICE_RECOGNITION_TIMEOUT = 5000;  // ms
1988
1989    private synchronized void expectVoiceRecognition() {
1990        mWaitingForVoiceRecognition = true;
1991        Message msg = Message.obtain(mHandler, CHECK_VOICE_RECOGNITION_STARTED);
1992        mHandler.sendMessageDelayed(msg, START_VOICE_RECOGNITION_TIMEOUT);
1993        if (!mStartVoiceRecognitionWakeLock.isHeld()) {
1994            mStartVoiceRecognitionWakeLock.acquire(START_VOICE_RECOGNITION_TIMEOUT);
1995        }
1996    }
1997
1998    /* package */ synchronized boolean startVoiceRecognition() {
1999        if (mWaitingForVoiceRecognition) {
2000            // HF initiated
2001            mWaitingForVoiceRecognition = false;
2002            sendURC("OK");
2003        } else {
2004            // AG initiated
2005            sendURC("+BVRA: 1");
2006        }
2007        boolean ret = audioOn();
2008        if (mStartVoiceRecognitionWakeLock.isHeld()) {
2009            mStartVoiceRecognitionWakeLock.release();
2010        }
2011        return ret;
2012    }
2013
2014    /* package */ synchronized boolean stopVoiceRecognition() {
2015        sendURC("+BVRA: 0");
2016        audioOff();
2017        return true;
2018    }
2019
2020    private boolean inDebug() {
2021        return DBG && SystemProperties.getBoolean(DebugThread.DEBUG_HANDSFREE, false);
2022    }
2023
2024    private boolean allowAudioAnytime() {
2025        return inDebug() && SystemProperties.getBoolean(DebugThread.DEBUG_HANDSFREE_AUDIO_ANYTIME,
2026                false);
2027    }
2028
2029    private void startDebug() {
2030        if (DBG && mDebugThread == null) {
2031            mDebugThread = new DebugThread();
2032            mDebugThread.start();
2033        }
2034    }
2035
2036    private void stopDebug() {
2037        if (mDebugThread != null) {
2038            mDebugThread.interrupt();
2039            mDebugThread = null;
2040        }
2041    }
2042
2043    /** Debug thread to read debug properties - runs when debug.bt.hfp is true
2044     *  at the time a bluetooth handsfree device is connected. Debug properties
2045     *  are polled and mock updates sent every 1 second */
2046    private class DebugThread extends Thread {
2047        /** Turns on/off handsfree profile debugging mode */
2048        private static final String DEBUG_HANDSFREE = "debug.bt.hfp";
2049
2050        /** Mock battery level change - use 0 to 5 */
2051        private static final String DEBUG_HANDSFREE_BATTERY = "debug.bt.hfp.battery";
2052
2053        /** Mock no cellular service when false */
2054        private static final String DEBUG_HANDSFREE_SERVICE = "debug.bt.hfp.service";
2055
2056        /** Mock cellular roaming when true */
2057        private static final String DEBUG_HANDSFREE_ROAM = "debug.bt.hfp.roam";
2058
2059        /** false to true transition will force an audio (SCO) connection to
2060         *  be established. true to false will force audio to be disconnected
2061         */
2062        private static final String DEBUG_HANDSFREE_AUDIO = "debug.bt.hfp.audio";
2063
2064        /** true allows incoming SCO connection out of call.
2065         */
2066        private static final String DEBUG_HANDSFREE_AUDIO_ANYTIME = "debug.bt.hfp.audio_anytime";
2067
2068        /** Mock signal strength change in ASU - use 0 to 31 */
2069        private static final String DEBUG_HANDSFREE_SIGNAL = "debug.bt.hfp.signal";
2070
2071        /** Debug AT+CLCC: print +CLCC result */
2072        private static final String DEBUG_HANDSFREE_CLCC = "debug.bt.hfp.clcc";
2073
2074        /** Debug AT+BSIR - Send In Band Ringtones Unsolicited AT command.
2075         * debug.bt.unsol.inband = 0 => AT+BSIR = 0 sent by the AG
2076         * debug.bt.unsol.inband = 1 => AT+BSIR = 0 sent by the AG
2077         * Other values are ignored.
2078         */
2079
2080        private static final String DEBUG_UNSOL_INBAND_RINGTONE =
2081            "debug.bt.unsol.inband";
2082
2083        @Override
2084        public void run() {
2085            boolean oldService = true;
2086            boolean oldRoam = false;
2087            boolean oldAudio = false;
2088
2089            while (!isInterrupted() && inDebug()) {
2090                int batteryLevel = SystemProperties.getInt(DEBUG_HANDSFREE_BATTERY, -1);
2091                if (batteryLevel >= 0 && batteryLevel <= 5) {
2092                    Intent intent = new Intent();
2093                    intent.putExtra("level", batteryLevel);
2094                    intent.putExtra("scale", 5);
2095                    mBluetoothPhoneState.updateBatteryState(intent);
2096                }
2097
2098                boolean serviceStateChanged = false;
2099                if (SystemProperties.getBoolean(DEBUG_HANDSFREE_SERVICE, true) != oldService) {
2100                    oldService = !oldService;
2101                    serviceStateChanged = true;
2102                }
2103                if (SystemProperties.getBoolean(DEBUG_HANDSFREE_ROAM, false) != oldRoam) {
2104                    oldRoam = !oldRoam;
2105                    serviceStateChanged = true;
2106                }
2107                if (serviceStateChanged) {
2108                    Bundle b = new Bundle();
2109                    b.putInt("state", oldService ? 0 : 1);
2110                    b.putBoolean("roaming", oldRoam);
2111                    mBluetoothPhoneState.updateServiceState(true, ServiceState.newFromBundle(b));
2112                }
2113
2114                if (SystemProperties.getBoolean(DEBUG_HANDSFREE_AUDIO, false) != oldAudio) {
2115                    oldAudio = !oldAudio;
2116                    if (oldAudio) {
2117                        audioOn();
2118                    } else {
2119                        audioOff();
2120                    }
2121                }
2122
2123                int signalLevel = SystemProperties.getInt(DEBUG_HANDSFREE_SIGNAL, -1);
2124                if (signalLevel >= 0 && signalLevel <= 31) {
2125                    SignalStrength signalStrength = new SignalStrength(signalLevel, -1, -1, -1,
2126                            -1, -1, -1, true);
2127                    Intent intent = new Intent();
2128                    Bundle data = new Bundle();
2129                    signalStrength.fillInNotifierBundle(data);
2130                    intent.putExtras(data);
2131                    mBluetoothPhoneState.updateSignalState(intent);
2132                }
2133
2134                if (SystemProperties.getBoolean(DEBUG_HANDSFREE_CLCC, false)) {
2135                    log(gsmGetClccResult().toString());
2136                }
2137                try {
2138                    sleep(1000);  // 1 second
2139                } catch (InterruptedException e) {
2140                    break;
2141                }
2142
2143                int inBandRing =
2144                    SystemProperties.getInt(DEBUG_UNSOL_INBAND_RINGTONE, -1);
2145                if (inBandRing == 0 || inBandRing == 1) {
2146                    AtCommandResult result =
2147                        new AtCommandResult(AtCommandResult.UNSOLICITED);
2148                    result.addResponse("+BSIR: " + inBandRing);
2149                    sendURC(result.toString());
2150                }
2151            }
2152        }
2153    }
2154
2155    public void cdmaSwapSecondCallState() {
2156        if (DBG) log("cdmaSetSecondCallState: Toggling mCdmaIsSecondCallActive");
2157        mCdmaIsSecondCallActive = !mCdmaIsSecondCallActive;
2158    }
2159
2160    public void cdmaSetSecondCallState(boolean state) {
2161        if (DBG) log("cdmaSetSecondCallState: Setting mCdmaIsSecondCallActive to " + state);
2162        mCdmaIsSecondCallActive = state;
2163    }
2164
2165    private static void log(String msg) {
2166        Log.d(TAG, msg);
2167    }
2168}
2169