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