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