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