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