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