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