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