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