BluetoothHandsfree.java revision 404edc94de563aef5fd5ba48be9114a970cb93bb
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.BluetoothDevice;
23import android.bluetooth.BluetoothHeadset;
24import android.bluetooth.BluetoothIntent;
25import android.bluetooth.HeadsetBase;
26import android.bluetooth.ScoSocket;
27import android.content.ActivityNotFoundException;
28import android.content.BroadcastReceiver;
29import android.content.Context;
30import android.content.Intent;
31import android.content.IntentFilter;
32import android.media.AudioManager;
33import android.net.Uri;
34import android.os.AsyncResult;
35import android.os.Bundle;
36import android.os.Handler;
37import android.os.Message;
38import android.os.PowerManager;
39import android.os.PowerManager.WakeLock;
40import android.os.SystemProperties;
41import android.telephony.PhoneNumberUtils;
42import android.telephony.ServiceState;
43import android.telephony.SignalStrength;
44import android.util.Log;
45
46import com.android.internal.telephony.Call;
47import com.android.internal.telephony.Connection;
48import com.android.internal.telephony.Phone;
49import com.android.internal.telephony.TelephonyIntents;
50
51import java.util.LinkedList;
52/**
53 * Bluetooth headset manager for the Phone app.
54 * @hide
55 */
56public class BluetoothHandsfree {
57    private static final String TAG = "BT HS/HF";
58    private static final boolean DBG = false;
59    private static final boolean VDBG = false;  // even more logging
60
61    public static final int TYPE_UNKNOWN           = 0;
62    public static final int TYPE_HEADSET           = 1;
63    public static final int TYPE_HANDSFREE         = 2;
64
65    private final Context mContext;
66    private final Phone mPhone;
67    private ServiceState mServiceState;
68    private HeadsetBase mHeadset;  // null when not connected
69    private int mHeadsetType;
70    private boolean mAudioPossible;
71    private ScoSocket mIncomingSco;
72    private ScoSocket mOutgoingSco;
73    private ScoSocket mConnectedSco;
74
75    private Call mForegroundCall;
76    private Call mBackgroundCall;
77    private Call mRingingCall;
78
79    private AudioManager mAudioManager;
80    private PowerManager mPowerManager;
81
82    private boolean mUserWantsAudio;
83    private WakeLock mStartCallWakeLock;  // held while waiting for the intent to start call
84    private WakeLock mStartVoiceRecognitionWakeLock;  // held while waiting for voice recognition
85
86    // AT command state
87    private static final int MAX_CONNECTIONS = 6;  // Max connections allowed by GSM
88
89    private long mBgndEarliestConnectionTime = 0;
90    private boolean mClip = false;  // Calling Line Information Presentation
91    private boolean mIndicatorsEnabled = false;
92    private boolean mCmee = false;  // Extended Error reporting
93    private long[] mClccTimestamps; // Timestamps associated with each clcc index
94    private boolean[] mClccUsed;     // Is this clcc index in use
95    private boolean mWaitingForCallStart;
96    private boolean mWaitingForVoiceRecognition;
97
98    private final BluetoothPhoneState mPhoneState;  // for CIND and CIEV updates
99    private final BluetoothAtPhonebook mPhonebook;
100
101    private DebugThread mDebugThread;
102    private int mScoGain = Integer.MIN_VALUE;
103
104    private static Intent sVoiceCommandIntent;
105
106    // Audio parameters
107    private static final String HEADSET_NREC = "bt_headset_nrec";
108    private static final String HEADSET_NAME = "bt_headset_name";
109
110    private int mRemoteBrsf = 0;
111    private int mLocalBrsf = 0;
112
113    /* Constants from Bluetooth Specification Hands-Free profile version 1.5 */
114    private static final int BRSF_AG_THREE_WAY_CALLING = 1 << 0;
115    private static final int BRSF_AG_EC_NR = 1 << 1;
116    private static final int BRSF_AG_VOICE_RECOG = 1 << 2;
117    private static final int BRSF_AG_IN_BAND_RING = 1 << 3;
118    private static final int BRSF_AG_VOICE_TAG_NUMBE = 1 << 4;
119    private static final int BRSF_AG_REJECT_CALL = 1 << 5;
120    private static final int BRSF_AG_ENHANCED_CALL_STATUS = 1 <<  6;
121    private static final int BRSF_AG_ENHANCED_CALL_CONTROL = 1 << 7;
122    private static final int BRSF_AG_ENHANCED_ERR_RESULT_CODES = 1 << 8;
123
124    private static final int BRSF_HF_EC_NR = 1 << 0;
125    private static final int BRSF_HF_CW_THREE_WAY_CALLING = 1 << 1;
126    private static final int BRSF_HF_CLIP = 1 << 2;
127    private static final int BRSF_HF_VOICE_REG_ACT = 1 << 3;
128    private static final int BRSF_HF_REMOTE_VOL_CONTROL = 1 << 4;
129    private static final int BRSF_HF_ENHANCED_CALL_STATUS = 1 <<  5;
130    private static final int BRSF_HF_ENHANCED_CALL_CONTROL = 1 << 6;
131
132    public static String typeToString(int type) {
133        switch (type) {
134        case TYPE_UNKNOWN:
135            return "unknown";
136        case TYPE_HEADSET:
137            return "headset";
138        case TYPE_HANDSFREE:
139            return "handsfree";
140        }
141        return null;
142    }
143
144    public BluetoothHandsfree(Context context, Phone phone) {
145        mPhone = phone;
146        mContext = context;
147        BluetoothDevice bluetooth =
148                (BluetoothDevice)context.getSystemService(Context.BLUETOOTH_SERVICE);
149        boolean bluetoothCapable = (bluetooth != null);
150        mHeadset = null;  // nothing connected yet
151
152        mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
153        mStartCallWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
154                                                       TAG + ":StartCall");
155        mStartCallWakeLock.setReferenceCounted(false);
156        mStartVoiceRecognitionWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
157                                                       TAG + ":VoiceRecognition");
158        mStartVoiceRecognitionWakeLock.setReferenceCounted(false);
159
160        mLocalBrsf = BRSF_AG_THREE_WAY_CALLING |
161                     BRSF_AG_EC_NR |
162                     BRSF_AG_REJECT_CALL |
163                     BRSF_AG_ENHANCED_CALL_STATUS;
164
165        if (sVoiceCommandIntent == null) {
166            sVoiceCommandIntent = new Intent(Intent.ACTION_VOICE_COMMAND);
167            sVoiceCommandIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
168        }
169        if (mContext.getPackageManager().resolveActivity(sVoiceCommandIntent, 0) != null &&
170                !BluetoothHeadset.DISABLE_BT_VOICE_DIALING) {
171            mLocalBrsf |= BRSF_AG_VOICE_RECOG;
172        }
173
174        if (bluetoothCapable) {
175            resetAtState();
176        }
177
178        mRingingCall = mPhone.getRingingCall();
179        mForegroundCall = mPhone.getForegroundCall();
180        mBackgroundCall = mPhone.getBackgroundCall();
181        mPhoneState = new BluetoothPhoneState();
182        mUserWantsAudio = true;
183        mPhonebook = new BluetoothAtPhonebook(mContext, this);
184        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
185    }
186
187    /* package */ synchronized void onBluetoothEnabled() {
188        /* Bluez has a bug where it will always accept and then orphan
189         * incoming SCO connections, regardless of whether we have a listening
190         * SCO socket. So the best thing to do is always run a listening socket
191         * while bluetooth is on so that at least we can diconnect it
192         * immediately when we don't want it.
193         */
194        if (mIncomingSco == null) {
195            mIncomingSco = createScoSocket();
196            mIncomingSco.accept();
197        }
198    }
199
200    /* package */ synchronized void onBluetoothDisabled() {
201        if (mConnectedSco != null) {
202            mAudioManager.setBluetoothScoOn(false);
203            broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_DISCONNECTED);
204            mConnectedSco.close();
205            mConnectedSco = null;
206        }
207        if (mOutgoingSco != null) {
208            mOutgoingSco.close();
209            mOutgoingSco = null;
210        }
211        if (mIncomingSco != null) {
212            mIncomingSco.close();
213            mIncomingSco = null;
214        }
215    }
216
217    private boolean isHeadsetConnected() {
218        if (mHeadset == null) {
219            return false;
220        }
221        return mHeadset.isConnected();
222    }
223
224    /* package */ void connectHeadset(HeadsetBase headset, int headsetType) {
225        mHeadset = headset;
226        mHeadsetType = headsetType;
227        if (mHeadsetType == TYPE_HEADSET) {
228            initializeHeadsetAtParser();
229        } else {
230            initializeHandsfreeAtParser();
231        }
232        headset.startEventThread();
233        configAudioParameters();
234
235        if (inDebug()) {
236            startDebug();
237        }
238
239        if (isIncallAudio()) {
240            audioOn();
241        }
242    }
243
244    /* returns true if there is some kind of in-call audio we may wish to route
245     * bluetooth to */
246    private boolean isIncallAudio() {
247        Call.State state = mForegroundCall.getState();
248
249        return (state == Call.State.ACTIVE || state == Call.State.ALERTING);
250    }
251
252    /* package */ void disconnectHeadset() {
253        mHeadset = null;
254        stopDebug();
255        resetAtState();
256    }
257
258    private void resetAtState() {
259        mClip = false;
260        mIndicatorsEnabled = false;
261        mCmee = false;
262        mClccTimestamps = new long[MAX_CONNECTIONS];
263        mClccUsed = new boolean[MAX_CONNECTIONS];
264        for (int i = 0; i < MAX_CONNECTIONS; i++) {
265            mClccUsed[i] = false;
266        }
267        mRemoteBrsf = 0;
268    }
269
270    private void configAudioParameters() {
271        String name = mHeadset.getName();
272        if (name == null) {
273            name = "<unknown>";
274        }
275        mAudioManager.setParameter(HEADSET_NAME, name);
276        mAudioManager.setParameter(HEADSET_NREC, "on");
277    }
278
279
280    /** Represents the data that we send in a +CIND or +CIEV command to the HF
281     */
282    private class BluetoothPhoneState {
283        // 0: no service
284        // 1: service
285        private int mService;
286
287        // 0: no active call
288        // 1: active call (where active means audio is routed - not held call)
289        private int mCall;
290
291        // 0: not in call setup
292        // 1: incoming call setup
293        // 2: outgoing call setup
294        // 3: remote party being alerted in an outgoing call setup
295        private int mCallsetup;
296
297        // 0: no calls held
298        // 1: held call and active call
299        // 2: held call only
300        private int mCallheld;
301
302        // cellular signal strength of AG: 0-5
303        private int mSignal;
304
305        // cellular signal strength in CSQ rssi scale
306        private int mRssi;  // for CSQ
307
308        // 0: roaming not active (home)
309        // 1: roaming active
310        private int mRoam;
311
312        // battery charge of AG: 0-5
313        private int mBattchg;
314
315        // 0: not registered
316        // 1: registered, home network
317        // 5: registered, roaming
318        private int mStat;  // for CREG
319
320        private String mRingingNumber;  // Context for in-progress RING's
321        private int    mRingingType;
322        private boolean mIgnoreRing = false;
323
324        private static final int SERVICE_STATE_CHANGED = 1;
325        private static final int PHONE_STATE_CHANGED = 2;
326        private static final int RING = 3;
327
328        private Handler mStateChangeHandler = new Handler() {
329            @Override
330            public void handleMessage(Message msg) {
331                switch(msg.what) {
332                case RING:
333                    AtCommandResult result = ring();
334                    if (result != null) {
335                        sendURC(result.toString());
336                    }
337                    break;
338                case SERVICE_STATE_CHANGED:
339                    ServiceState state = (ServiceState) ((AsyncResult) msg.obj).result;
340                    updateServiceState(sendUpdate(), state);
341                    break;
342                case PHONE_STATE_CHANGED:
343                    Connection connection = null;
344                    if (((AsyncResult) msg.obj).result instanceof Connection) {
345                        connection = (Connection) ((AsyncResult) msg.obj).result;
346                    }
347                    updatePhoneState(sendUpdate(), connection);
348                    break;
349                }
350            }
351        };
352
353        private BluetoothPhoneState() {
354            // init members
355            updateServiceState(false, mPhone.getServiceState());
356            updatePhoneState(false, null);
357            mBattchg = 5;  // There is currently no API to get battery level
358                           // on demand, so set to 5 and wait for an update
359            mSignal = asuToSignal(mPhone.getSignalStrength());
360
361            // register for updates
362            mPhone.registerForServiceStateChanged(mStateChangeHandler,
363                                                  SERVICE_STATE_CHANGED, null);
364            mPhone.registerForPhoneStateChanged(mStateChangeHandler,
365                                                PHONE_STATE_CHANGED, null);
366            IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
367            filter.addAction(TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED);
368            mContext.registerReceiver(mStateReceiver, filter);
369        }
370
371        private void updateBtPhoneStateAfterRadioTechnologyChange() {
372            if(DBG) Log.d(TAG, "updateBtPhoneStateAfterRadioTechnologyChange...");
373
374            //Unregister all events from the old obsolete phone
375            mPhone.unregisterForServiceStateChanged(mStateChangeHandler);
376            mPhone.unregisterForPhoneStateChanged(mStateChangeHandler);
377
378            //Register all events new to the new active phone
379            mPhone.registerForServiceStateChanged(mStateChangeHandler, SERVICE_STATE_CHANGED, null);
380            mPhone.registerForPhoneStateChanged(mStateChangeHandler, PHONE_STATE_CHANGED, null);
381        }
382
383        private boolean sendUpdate() {
384            return isHeadsetConnected() && mHeadsetType == TYPE_HANDSFREE && mIndicatorsEnabled;
385        }
386
387        private boolean sendClipUpdate() {
388            return isHeadsetConnected() && mHeadsetType == TYPE_HANDSFREE && mClip;
389        }
390
391        /* convert [0,31] ASU signal strength to the [0,5] expected by
392         * bluetooth devices. Scale is similar to status bar policy
393         */
394        private int gsmAsuToSignal(int asu) {
395            if      (asu >= 16) return 5;
396            else if (asu >= 8)  return 4;
397            else if (asu >= 4)  return 3;
398            else if (asu >= 2)  return 2;
399            else if (asu >= 1)  return 1;
400            else                return 0;
401        }
402
403        /* convert cdma dBm signal strength to the [0,5] expected by
404         * bluetooth devices. Scale is similar to status bar policy
405         */
406        private int cdmaDbmToSignal(int cdmaDbm) {
407            if (cdmaDbm >= -75)       return 5;
408            else if (cdmaDbm >= -85)  return 4;
409            else if (cdmaDbm >= -95)  return 3;
410            else if (cdmaDbm >= -100) return 2;
411            else if (cdmaDbm >= -105) return 2;
412            else return 0;
413        }
414
415
416        private int asuToSignal(SignalStrength signalStrength) {
417            if (!signalStrength.isGsm()) {
418                return gsmAsuToSignal(signalStrength.getCdmaDbm());
419            } else {
420                return cdmaDbmToSignal(signalStrength.getGsmSignalStrength());
421            }
422        }
423
424
425        /* convert [0,5] signal strength to a rssi signal strength for CSQ
426         * which is [0,31]. Despite the same scale, this is not the same value
427         * as ASU.
428         */
429        private int signalToRssi(int signal) {
430            // using C4A suggested values
431            switch (signal) {
432            case 0: return 0;
433            case 1: return 4;
434            case 2: return 8;
435            case 3: return 13;
436            case 4: return 19;
437            case 5: return 31;
438            }
439            return 0;
440        }
441
442
443        private final BroadcastReceiver mStateReceiver = new BroadcastReceiver() {
444            @Override
445            public void onReceive(Context context, Intent intent) {
446                if (intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) {
447                    updateBatteryState(intent);
448                } else if (intent.getAction().equals(TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED)) {
449                    updateSignalState(intent);
450                }
451            }
452        };
453
454        private synchronized void updateBatteryState(Intent intent) {
455            int batteryLevel = intent.getIntExtra("level", -1);
456            int scale = intent.getIntExtra("scale", -1);
457            if (batteryLevel == -1 || scale == -1) {
458                return;  // ignore
459            }
460            batteryLevel = batteryLevel * 5 / scale;
461            if (mBattchg != batteryLevel) {
462                mBattchg = batteryLevel;
463                if (sendUpdate()) {
464                    sendURC("+CIEV: 7," + mBattchg);
465                }
466            }
467        }
468
469        private synchronized void updateSignalState(Intent intent) {
470            // NOTE this function is called by the BroadcastReceiver mStateReceiver after intent
471            // ACTION_SIGNAL_STRENGTH_CHANGED and by the DebugThread mDebugThread
472            SignalStrength signalStrength = SignalStrength.newFromBundle(intent.getExtras());
473            int signal;
474
475            if (signalStrength != null) {
476                signal = asuToSignal(signalStrength);
477                mRssi = signalToRssi(signal);  // no unsolicited CSQ
478                if (signal != mSignal) {
479                    mSignal = signal;
480                    if (sendUpdate()) {
481                        sendURC("+CIEV: 5," + mSignal);
482                    }
483                }
484            } else {
485                Log.e(TAG, "Signal Strength null");
486            }
487        }
488
489        private synchronized void updateServiceState(boolean sendUpdate, ServiceState state) {
490            int service = state.getState() == ServiceState.STATE_IN_SERVICE ? 1 : 0;
491            int roam = state.getRoaming() ? 1 : 0;
492            int stat;
493            AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
494
495            if (service == 0) {
496                stat = 0;
497            } else {
498                stat = (roam == 1) ? 5 : 1;
499            }
500
501            if (service != mService) {
502                mService = service;
503                if (sendUpdate) {
504                    result.addResponse("+CIEV: 1," + mService);
505                }
506            }
507            if (roam != mRoam) {
508                mRoam = roam;
509                if (sendUpdate) {
510                    result.addResponse("+CIEV: 6," + mRoam);
511                }
512            }
513            if (stat != mStat) {
514                mStat = stat;
515                if (sendUpdate) {
516                    result.addResponse(toCregString());
517                }
518            }
519
520            sendURC(result.toString());
521        }
522
523        private synchronized void updatePhoneState(boolean sendUpdate, Connection connection) {
524            int call = 0;
525            int callsetup = 0;
526            int callheld = 0;
527            int prevCallsetup = mCallsetup;
528            AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
529
530            if (DBG) log("updatePhoneState()");
531
532            switch (mPhone.getState()) {
533            case IDLE:
534                mUserWantsAudio = true;  // out of call - reset state
535                audioOff();
536                break;
537            default:
538                callStarted();
539            }
540
541            switch(mForegroundCall.getState()) {
542            case ACTIVE:
543                call = 1;
544                mAudioPossible = true;
545                break;
546            case DIALING:
547                callsetup = 2;
548                mAudioPossible = false;
549                break;
550            case ALERTING:
551                callsetup = 3;
552                // Open the SCO channel for the outgoing call.
553                audioOn();
554                mAudioPossible = true;
555                break;
556            default:
557                mAudioPossible = false;
558            }
559
560            switch(mRingingCall.getState()) {
561            case INCOMING:
562            case WAITING:
563                callsetup = 1;
564                break;
565            }
566
567            switch(mBackgroundCall.getState()) {
568            case HOLDING:
569                if (call == 1) {
570                    callheld = 1;
571                } else {
572                    call = 1;
573                    callheld = 2;
574                }
575                break;
576            }
577
578            if (mCall != call) {
579                if (call == 1) {
580                    // This means that a call has transitioned from NOT ACTIVE to ACTIVE.
581                    // Switch on audio.
582                    audioOn();
583                }
584                mCall = call;
585                if (sendUpdate) {
586                    result.addResponse("+CIEV: 2," + mCall);
587                }
588            }
589            if (mCallsetup != callsetup) {
590                mCallsetup = callsetup;
591                if (sendUpdate) {
592                    // If mCall = 0, send CIEV
593                    // mCall = 1, mCallsetup = 0, send CIEV
594                    // mCall = 1, mCallsetup = 1, send CIEV after CCWA,
595                    // if 3 way supported.
596                    // mCall = 1, mCallsetup = 2 / 3 -> send CIEV,
597                    // if 3 way is supported
598                    if (mCall != 1 || mCallsetup == 0 ||
599                        mCallsetup != 1 && (mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) {
600                        result.addResponse("+CIEV: 3," + mCallsetup);
601                    }
602                }
603            }
604
605            boolean callsSwitched =
606                (callheld == 1 && ! (mBackgroundCall.getEarliestConnectTime() ==
607                    mBgndEarliestConnectionTime));
608
609            mBgndEarliestConnectionTime = mBackgroundCall.getEarliestConnectTime();
610
611            if (mCallheld != callheld || callsSwitched) {
612                mCallheld = callheld;
613                if (sendUpdate) {
614                    result.addResponse("+CIEV: 4," + mCallheld);
615                }
616            }
617
618            if (callsetup == 1 && callsetup != prevCallsetup) {
619                // new incoming call
620                String number = null;
621                int type = 128;
622                // find incoming phone number and type
623                if (connection == null) {
624                    connection = mRingingCall.getEarliestConnection();
625                    if (connection == null) {
626                        Log.e(TAG, "Could not get a handle on Connection object for new " +
627                              "incoming call");
628                    }
629                }
630                if (connection != null) {
631                    number = connection.getAddress();
632                    if (number != null) {
633                        type = PhoneNumberUtils.toaFromString(number);
634                    }
635                }
636                if (number == null) {
637                    number = "";
638                }
639                if ((call != 0 || callheld != 0) && sendUpdate) {
640                    // call waiting
641                    if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) {
642                        result.addResponse("+CCWA: \"" + number + "\"," + type);
643                        result.addResponse("+CIEV: 3," + callsetup);
644                    }
645                } else {
646                    // regular new incoming call
647                    mRingingNumber = number;
648                    mRingingType = type;
649                    mIgnoreRing = false;
650
651                    if ((mLocalBrsf & BRSF_AG_IN_BAND_RING) == 0x1) {
652                        audioOn();
653                    }
654                    result.addResult(ring());
655                }
656            }
657            sendURC(result.toString());
658        }
659
660        private AtCommandResult ring() {
661            if (!mIgnoreRing && mRingingCall.isRinging()) {
662                AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
663                result.addResponse("RING");
664                if (sendClipUpdate()) {
665                    result.addResponse("+CLIP: \"" + mRingingNumber + "\"," + mRingingType);
666                }
667
668                Message msg = mStateChangeHandler.obtainMessage(RING);
669                mStateChangeHandler.sendMessageDelayed(msg, 3000);
670                return result;
671            }
672            return null;
673        }
674
675        private synchronized String toCregString() {
676            return new String("+CREG: 1," + mStat);
677        }
678
679        private synchronized AtCommandResult toCindResult() {
680            AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
681            String status = "+CIND: " + mService + "," + mCall + "," + mCallsetup + "," +
682                            mCallheld + "," + mSignal + "," + mRoam + "," + mBattchg;
683            result.addResponse(status);
684            return result;
685        }
686
687        private synchronized AtCommandResult toCsqResult() {
688            AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
689            String status = "+CSQ: " + mRssi + ",99";
690            result.addResponse(status);
691            return result;
692        }
693
694
695        private synchronized AtCommandResult getCindTestResult() {
696            return new AtCommandResult("+CIND: (\"service\",(0-1))," + "(\"call\",(0-1))," +
697                        "(\"callsetup\",(0-3)),(\"callheld\",(0-2)),(\"signal\",(0-5))," +
698                        "(\"roam\",(0-1)),(\"battchg\",(0-5))");
699        }
700
701        private synchronized void ignoreRing() {
702            mCallsetup = 0;
703            mIgnoreRing = true;
704            if (sendUpdate()) {
705                sendURC("+CIEV: 3," + mCallsetup);
706            }
707        }
708
709    };
710
711    private static final int SCO_ACCEPTED = 1;
712    private static final int SCO_CONNECTED = 2;
713    private static final int SCO_CLOSED = 3;
714    private static final int CHECK_CALL_STARTED = 4;
715    private static final int CHECK_VOICE_RECOGNITION_STARTED = 5;
716
717    private final Handler mHandler = new Handler() {
718        @Override
719        public synchronized void handleMessage(Message msg) {
720            switch (msg.what) {
721            case SCO_ACCEPTED:
722                if (msg.arg1 == ScoSocket.STATE_CONNECTED) {
723                    if (isHeadsetConnected() && (mAudioPossible || allowAudioAnytime()) &&
724                            mConnectedSco == null) {
725                        Log.i(TAG, "Routing audio for incoming SCO connection");
726                        mConnectedSco = (ScoSocket)msg.obj;
727                        mAudioManager.setBluetoothScoOn(true);
728                        broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_CONNECTED);
729                    } else {
730                        Log.i(TAG, "Rejecting incoming SCO connection");
731                        ((ScoSocket)msg.obj).close();
732                    }
733                } // else error trying to accept, try again
734                mIncomingSco = createScoSocket();
735                mIncomingSco.accept();
736                break;
737            case SCO_CONNECTED:
738                if (msg.arg1 == ScoSocket.STATE_CONNECTED && isHeadsetConnected() &&
739                        mConnectedSco == null) {
740                    if (DBG) log("Routing audio for outgoing SCO conection");
741                    mConnectedSco = (ScoSocket)msg.obj;
742                    mAudioManager.setBluetoothScoOn(true);
743                    broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_CONNECTED);
744                } else if (msg.arg1 == ScoSocket.STATE_CONNECTED) {
745                    if (DBG) log("Rejecting new connected outgoing SCO socket");
746                    ((ScoSocket)msg.obj).close();
747                    mOutgoingSco.close();
748                }
749                mOutgoingSco = null;
750                break;
751            case SCO_CLOSED:
752                if (mConnectedSco == (ScoSocket)msg.obj) {
753                    mConnectedSco = null;
754                    mAudioManager.setBluetoothScoOn(false);
755                    broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_DISCONNECTED);
756                } else if (mOutgoingSco == (ScoSocket)msg.obj) {
757                    mOutgoingSco = null;
758                } else if (mIncomingSco == (ScoSocket)msg.obj) {
759                    mIncomingSco = null;
760                }
761                break;
762            case CHECK_CALL_STARTED:
763                if (mWaitingForCallStart) {
764                    mWaitingForCallStart = false;
765                    Log.e(TAG, "Timeout waiting for call to start");
766                    sendURC("ERROR");
767                    if (mStartCallWakeLock.isHeld()) {
768                        mStartCallWakeLock.release();
769                    }
770                }
771                break;
772            case CHECK_VOICE_RECOGNITION_STARTED:
773                if (mWaitingForVoiceRecognition) {
774                    mWaitingForVoiceRecognition = false;
775                    Log.e(TAG, "Timeout waiting for voice recognition to start");
776                    sendURC("ERROR");
777                }
778                break;
779            }
780        }
781    };
782
783    private ScoSocket createScoSocket() {
784        return new ScoSocket(mPowerManager, mHandler, SCO_ACCEPTED, SCO_CONNECTED, SCO_CLOSED);
785    }
786
787    private void broadcastAudioStateIntent(int state) {
788        if (VDBG) log("broadcastAudioStateIntent(" + state + ")");
789        Intent intent = new Intent(BluetoothIntent.HEADSET_AUDIO_STATE_CHANGED_ACTION);
790        intent.putExtra(BluetoothIntent.HEADSET_AUDIO_STATE, state);
791        mContext.sendBroadcast(intent, android.Manifest.permission.BLUETOOTH);
792    }
793
794
795    void updateBtHandsfreeAfterRadioTechnologyChange() {
796        if(DBG) Log.d(TAG, "updateBtHandsfreeAfterRadioTechnologyChange...");
797
798        //Get the Call references from the new active phone again
799        mRingingCall = mPhone.getRingingCall();
800        mForegroundCall = mPhone.getForegroundCall();
801        mBackgroundCall = mPhone.getBackgroundCall();
802
803        mPhoneState.updateBtPhoneStateAfterRadioTechnologyChange();
804    }
805
806    /** Request to establish SCO (audio) connection to bluetooth
807     * headset/handsfree, if one is connected. Does not block.
808     * Returns false if the user has requested audio off, or if there
809     * is some other immediate problem that will prevent BT audio.
810     */
811    /* package */ synchronized boolean audioOn() {
812        if (VDBG) log("audioOn()");
813        if (!isHeadsetConnected()) {
814            if (DBG) log("audioOn(): headset is not connected!");
815            return false;
816        }
817
818        if (mConnectedSco != null) {
819            if (DBG) log("audioOn(): audio is already connected");
820            return true;
821        }
822
823        if (!mUserWantsAudio) {
824            if (DBG) log("audioOn(): user requested no audio, ignoring");
825            return false;
826        }
827
828        if (mOutgoingSco != null) {
829            if (DBG) log("audioOn(): outgoing SCO already in progress");
830            return true;
831        }
832        mOutgoingSco = createScoSocket();
833        if (!mOutgoingSco.connect(mHeadset.getAddress())) {
834            mOutgoingSco = null;
835        }
836
837        return true;
838    }
839
840    /** Used to indicate the user requested BT audio on.
841     *  This will establish SCO (BT audio), even if the user requested it off
842     *  previously on this call.
843     */
844    /* package */ synchronized void userWantsAudioOn() {
845        mUserWantsAudio = true;
846        audioOn();
847    }
848    /** Used to indicate the user requested BT audio off.
849     *  This will prevent us from establishing BT audio again during this call
850     *  if audioOn() is called.
851     */
852    /* package */ synchronized void userWantsAudioOff() {
853        mUserWantsAudio = false;
854        audioOff();
855    }
856
857    /** Request to disconnect SCO (audio) connection to bluetooth
858     * headset/handsfree, if one is connected. Does not block.
859     */
860    /* package */ synchronized void audioOff() {
861        if (VDBG) log("audioOff()");
862
863        if (mConnectedSco != null) {
864            mAudioManager.setBluetoothScoOn(false);
865            broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_DISCONNECTED);
866            mConnectedSco.close();
867            mConnectedSco = null;
868        }
869        if (mOutgoingSco != null) {
870            mOutgoingSco.close();
871            mOutgoingSco = null;
872        }
873    }
874
875    /* package */ boolean isAudioOn() {
876        return (mConnectedSco != null);
877    }
878
879    /* package */ void ignoreRing() {
880        mPhoneState.ignoreRing();
881    }
882
883    private void sendURC(String urc) {
884        if (isHeadsetConnected()) {
885            mHeadset.sendURC(urc);
886        }
887    }
888
889    /** helper to redial last dialled number */
890    private AtCommandResult redial() {
891        String number = mPhonebook.getLastDialledNumber();
892        if (number == null) {
893            // spec seems to suggest sending ERROR if we dont have a
894            // number to redial
895            if (DBG) log("Bluetooth redial requested (+BLDN), but no previous " +
896                  "outgoing calls found. Ignoring");
897            return new AtCommandResult(AtCommandResult.ERROR);
898        }
899        Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
900                Uri.fromParts("tel", number, null));
901        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
902        mContext.startActivity(intent);
903
904        // We do not immediately respond OK, wait until we get a phone state
905        // update. If we return OK now and the handsfree immeidately requests
906        // our phone state it will say we are not in call yet which confuses
907        // some devices
908        expectCallStart();
909        return new AtCommandResult(AtCommandResult.UNSOLICITED);  // send nothing
910    }
911
912    /** Build the +CLCC result
913     *  The complexity arises from the fact that we need to maintain the same
914     *  CLCC index even as a call moves between states. */
915    private synchronized AtCommandResult getClccResult() {
916        // Collect all known connections
917        Connection[] clccConnections = new Connection[MAX_CONNECTIONS];  // indexed by CLCC index
918        LinkedList<Connection> newConnections = new LinkedList<Connection>();
919        LinkedList<Connection> connections = new LinkedList<Connection>();
920        if (mRingingCall.getState().isAlive()) {
921            connections.addAll(mRingingCall.getConnections());
922        }
923        if (mForegroundCall.getState().isAlive()) {
924            connections.addAll(mForegroundCall.getConnections());
925        }
926        if (mBackgroundCall.getState().isAlive()) {
927            connections.addAll(mBackgroundCall.getConnections());
928        }
929
930        // Mark connections that we already known about
931        boolean clccUsed[] = new boolean[MAX_CONNECTIONS];
932        for (int i = 0; i < MAX_CONNECTIONS; i++) {
933            clccUsed[i] = mClccUsed[i];
934            mClccUsed[i] = false;
935        }
936        for (Connection c : connections) {
937            boolean found = false;
938            long timestamp = c.getCreateTime();
939            for (int i = 0; i < MAX_CONNECTIONS; i++) {
940                if (clccUsed[i] && timestamp == mClccTimestamps[i]) {
941                    mClccUsed[i] = true;
942                    found = true;
943                    clccConnections[i] = c;
944                    break;
945                }
946            }
947            if (!found) {
948                newConnections.add(c);
949            }
950        }
951
952        // Find a CLCC index for new connections
953        while (!newConnections.isEmpty()) {
954            // Find lowest empty index
955            int i = 0;
956            while (mClccUsed[i]) i++;
957            // Find earliest connection
958            long earliestTimestamp = newConnections.get(0).getCreateTime();
959            Connection earliestConnection = newConnections.get(0);
960            for (int j = 0; j < newConnections.size(); j++) {
961                long timestamp = newConnections.get(j).getCreateTime();
962                if (timestamp < earliestTimestamp) {
963                    earliestTimestamp = timestamp;
964                    earliestConnection = newConnections.get(j);
965                }
966            }
967
968            // update
969            mClccUsed[i] = true;
970            mClccTimestamps[i] = earliestTimestamp;
971            clccConnections[i] = earliestConnection;
972            newConnections.remove(earliestConnection);
973        }
974
975        // Build CLCC
976        AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
977        for (int i = 0; i < clccConnections.length; i++) {
978            if (mClccUsed[i]) {
979                String clccEntry = connectionToClccEntry(i, clccConnections[i]);
980                if (clccEntry != null) {
981                    result.addResponse(clccEntry);
982                }
983            }
984        }
985
986        return result;
987    }
988
989    /** Convert a Connection object into a single +CLCC result */
990    private String connectionToClccEntry(int index, Connection c) {
991        int state;
992        switch (c.getState()) {
993        case ACTIVE:
994            state = 0;
995            break;
996        case HOLDING:
997            state = 1;
998            break;
999        case DIALING:
1000            state = 2;
1001            break;
1002        case ALERTING:
1003            state = 3;
1004            break;
1005        case INCOMING:
1006            state = 4;
1007            break;
1008        case WAITING:
1009            state = 5;
1010            break;
1011        default:
1012            return null;  // bad state
1013        }
1014
1015        int mpty = 0;
1016        Call call = c.getCall();
1017        if (call != null) {
1018            mpty = call.isMultiparty() ? 1 : 0;
1019        }
1020
1021        int direction = c.isIncoming() ? 1 : 0;
1022
1023        String number = c.getAddress();
1024        int type = -1;
1025        if (number != null) {
1026            type = PhoneNumberUtils.toaFromString(number);
1027        }
1028
1029        String result = "+CLCC: " + (index + 1) + "," + direction + "," + state + ",0," + mpty;
1030        if (number != null) {
1031            result += ",\"" + number + "\"," + type;
1032        }
1033        return result;
1034    }
1035    /**
1036     * Register AT Command handlers to implement the Headset profile
1037     */
1038    private void initializeHeadsetAtParser() {
1039        if (DBG) log("Registering Headset AT commands");
1040        AtParser parser = mHeadset.getAtParser();
1041        // Headset's usually only have one button, which is meant to cause the
1042        // HS to send us AT+CKPD=200 or AT+CKPD.
1043        parser.register("+CKPD", new AtCommandHandler() {
1044            private AtCommandResult headsetButtonPress() {
1045                if (mRingingCall.isRinging()) {
1046                    // Answer the call
1047                    PhoneUtils.answerCall(mPhone);
1048                    // If in-band ring tone is supported, SCO connection will already
1049                    // be up and the following call will just return.
1050                    audioOn();
1051                } else if (mForegroundCall.getState().isAlive()) {
1052                    if (!isAudioOn()) {
1053                        // Transfer audio from AG to HS
1054                        audioOn();
1055                    } else {
1056                        if (mHeadset.getDirection() == HeadsetBase.DIRECTION_INCOMING &&
1057                          (System.currentTimeMillis() - mHeadset.getConnectTimestamp()) < 5000) {
1058                            // Headset made a recent ACL connection to us - and
1059                            // made a mandatory AT+CKPD request to connect
1060                            // audio which races with our automatic audio
1061                            // setup.  ignore
1062                        } else {
1063                            // Hang up the call
1064                            audioOff();
1065                            PhoneUtils.hangup(mPhone);
1066                        }
1067                    }
1068                } else {
1069                    // No current call - redial last number
1070                    return redial();
1071                }
1072                return new AtCommandResult(AtCommandResult.OK);
1073            }
1074            @Override
1075            public AtCommandResult handleActionCommand() {
1076                return headsetButtonPress();
1077            }
1078            @Override
1079            public AtCommandResult handleSetCommand(Object[] args) {
1080                return headsetButtonPress();
1081            }
1082        });
1083    }
1084
1085    /**
1086     * Register AT Command handlers to implement the Handsfree profile
1087     */
1088    private void initializeHandsfreeAtParser() {
1089        if (DBG) log("Registering Handsfree AT commands");
1090        AtParser parser = mHeadset.getAtParser();
1091
1092        // Answer
1093        parser.register('A', new AtCommandHandler() {
1094            @Override
1095            public AtCommandResult handleBasicCommand(String args) {
1096                PhoneUtils.answerCall(mPhone);
1097                return new AtCommandResult(AtCommandResult.OK);
1098            }
1099        });
1100        parser.register('D', new AtCommandHandler() {
1101            @Override
1102            public AtCommandResult handleBasicCommand(String args) {
1103                if (args.length() > 0) {
1104                    if (args.charAt(0) == '>') {
1105                        // Yuck - memory dialling requested.
1106                        // Just dial last number for now
1107                        if (args.startsWith(">9999")) {   // for PTS test
1108                            return new AtCommandResult(AtCommandResult.ERROR);
1109                        }
1110                        return redial();
1111                    } else {
1112                        // Remove trailing ';'
1113                        if (args.charAt(args.length() - 1) == ';') {
1114                            args = args.substring(0, args.length() - 1);
1115                        }
1116                        Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1117                                Uri.fromParts("tel", args, null));
1118                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1119                        mContext.startActivity(intent);
1120
1121                        expectCallStart();
1122                        return new AtCommandResult(AtCommandResult.UNSOLICITED);  // send nothing
1123                    }
1124                }
1125                return new AtCommandResult(AtCommandResult.ERROR);
1126            }
1127        });
1128
1129        // Hang-up command
1130        parser.register("+CHUP", new AtCommandHandler() {
1131            @Override
1132            public AtCommandResult handleActionCommand() {
1133                if (!mForegroundCall.isIdle()) {
1134                    PhoneUtils.hangup(mForegroundCall);
1135                } else if (!mRingingCall.isIdle()) {
1136                    PhoneUtils.hangup(mRingingCall);
1137                } else if (!mBackgroundCall.isIdle()) {
1138                    PhoneUtils.hangup(mBackgroundCall);
1139                }
1140                return new AtCommandResult(AtCommandResult.OK);
1141            }
1142        });
1143
1144        // Bluetooth Retrieve Supported Features command
1145        parser.register("+BRSF", new AtCommandHandler() {
1146            private AtCommandResult sendBRSF() {
1147                return new AtCommandResult("+BRSF: " + mLocalBrsf);
1148            }
1149            @Override
1150            public AtCommandResult handleSetCommand(Object[] args) {
1151                // AT+BRSF=<handsfree supported features bitmap>
1152                // Handsfree is telling us which features it supports. We
1153                // send the features we support
1154                if (args.length == 1 && (args[0] instanceof Integer)) {
1155                    mRemoteBrsf = (Integer) args[0];
1156                } else {
1157                    Log.w(TAG, "HF didn't sent BRSF assuming 0");
1158                }
1159                return sendBRSF();
1160            }
1161            @Override
1162            public AtCommandResult handleActionCommand() {
1163                // This seems to be out of spec, but lets do the nice thing
1164                return sendBRSF();
1165            }
1166            @Override
1167            public AtCommandResult handleReadCommand() {
1168                // This seems to be out of spec, but lets do the nice thing
1169                return sendBRSF();
1170            }
1171        });
1172
1173        // Call waiting notification on/off
1174        parser.register("+CCWA", new AtCommandHandler() {
1175            @Override
1176            public AtCommandResult handleActionCommand() {
1177                // Seems to be out of spec, but lets return nicely
1178                return new AtCommandResult(AtCommandResult.OK);
1179            }
1180            @Override
1181            public AtCommandResult handleReadCommand() {
1182                // Call waiting is always on
1183                return new AtCommandResult("+CCWA: 1");
1184            }
1185            @Override
1186            public AtCommandResult handleSetCommand(Object[] args) {
1187                // AT+CCWA=<n>
1188                // Handsfree is trying to enable/disable call waiting. We
1189                // cannot disable in the current implementation.
1190                return new AtCommandResult(AtCommandResult.OK);
1191            }
1192            @Override
1193            public AtCommandResult handleTestCommand() {
1194                // Request for range of supported CCWA paramters
1195                return new AtCommandResult("+CCWA: (\"n\",(1))");
1196            }
1197        });
1198
1199        // Mobile Equipment Event Reporting enable/disable command
1200        // Of the full 3GPP syntax paramters (mode, keyp, disp, ind, bfr) we
1201        // only support paramter ind (disable/enable evert reporting using
1202        // +CDEV)
1203        parser.register("+CMER", new AtCommandHandler() {
1204            @Override
1205            public AtCommandResult handleReadCommand() {
1206                return new AtCommandResult(
1207                        "+CMER: 3,0,0," + (mIndicatorsEnabled ? "1" : "0"));
1208            }
1209            @Override
1210            public AtCommandResult handleSetCommand(Object[] args) {
1211                if (args.length < 4) {
1212                    // This is a syntax error
1213                    return new AtCommandResult(AtCommandResult.ERROR);
1214                } else if (args[0].equals(3) && args[1].equals(0) &&
1215                           args[2].equals(0)) {
1216                    if (args[3].equals(0)) {
1217                        mIndicatorsEnabled = false;
1218                        return new AtCommandResult(AtCommandResult.OK);
1219                    } else if (args[3].equals(1)) {
1220                        mIndicatorsEnabled = true;
1221                        return new AtCommandResult(AtCommandResult.OK);
1222                    }
1223                    return reportCmeError(BluetoothCmeError.OPERATION_NOT_SUPPORTED);
1224                } else {
1225                    return reportCmeError(BluetoothCmeError.OPERATION_NOT_SUPPORTED);
1226                }
1227            }
1228            @Override
1229            public AtCommandResult handleTestCommand() {
1230                return new AtCommandResult("+CMER: (3),(0),(0),(0-1)");
1231            }
1232        });
1233
1234        // Mobile Equipment Error Reporting enable/disable
1235        parser.register("+CMEE", new AtCommandHandler() {
1236            @Override
1237            public AtCommandResult handleActionCommand() {
1238                // out of spec, assume they want to enable
1239                mCmee = true;
1240                return new AtCommandResult(AtCommandResult.OK);
1241            }
1242            @Override
1243            public AtCommandResult handleReadCommand() {
1244                return new AtCommandResult("+CMEE: " + (mCmee ? "1" : "0"));
1245            }
1246            @Override
1247            public AtCommandResult handleSetCommand(Object[] args) {
1248                // AT+CMEE=<n>
1249                if (args.length == 0) {
1250                    // <n> ommitted - default to 0
1251                    mCmee = false;
1252                    return new AtCommandResult(AtCommandResult.OK);
1253                } else if (!(args[0] instanceof Integer)) {
1254                    // Syntax error
1255                    return new AtCommandResult(AtCommandResult.ERROR);
1256                } else {
1257                    mCmee = ((Integer)args[0] == 1);
1258                    return new AtCommandResult(AtCommandResult.OK);
1259                }
1260            }
1261            @Override
1262            public AtCommandResult handleTestCommand() {
1263                // Probably not required but spec, but no harm done
1264                return new AtCommandResult("+CMEE: (0-1)");
1265            }
1266        });
1267
1268        // Bluetooth Last Dialled Number
1269        parser.register("+BLDN", new AtCommandHandler() {
1270            @Override
1271            public AtCommandResult handleActionCommand() {
1272                return redial();
1273            }
1274        });
1275
1276        // Indicator Update command
1277        parser.register("+CIND", new AtCommandHandler() {
1278            @Override
1279            public AtCommandResult handleReadCommand() {
1280                return mPhoneState.toCindResult();
1281            }
1282            @Override
1283            public AtCommandResult handleTestCommand() {
1284                return mPhoneState.getCindTestResult();
1285            }
1286        });
1287
1288        // Query Signal Quality (legacy)
1289        parser.register("+CSQ", new AtCommandHandler() {
1290            @Override
1291            public AtCommandResult handleActionCommand() {
1292                return mPhoneState.toCsqResult();
1293            }
1294        });
1295
1296        // Query network registration state
1297        parser.register("+CREG", new AtCommandHandler() {
1298            @Override
1299            public AtCommandResult handleReadCommand() {
1300                return new AtCommandResult(mPhoneState.toCregString());
1301            }
1302        });
1303
1304        // Send DTMF. I don't know if we are also expected to play the DTMF tone
1305        // locally, right now we don't
1306        parser.register("+VTS", new AtCommandHandler() {
1307            @Override
1308            public AtCommandResult handleSetCommand(Object[] args) {
1309                if (args.length >= 1) {
1310                    char c;
1311                    if (args[0] instanceof Integer) {
1312                        c = ((Integer) args[0]).toString().charAt(0);
1313                    } else {
1314                        c = ((String) args[0]).charAt(0);
1315                    }
1316                    if (isValidDtmf(c)) {
1317                        mPhone.sendDtmf(c);
1318                        return new AtCommandResult(AtCommandResult.OK);
1319                    }
1320                }
1321                return new AtCommandResult(AtCommandResult.ERROR);
1322            }
1323            private boolean isValidDtmf(char c) {
1324                switch (c) {
1325                case '#':
1326                case '*':
1327                    return true;
1328                default:
1329                    if (Character.digit(c, 14) != -1) {
1330                        return true;  // 0-9 and A-D
1331                    }
1332                    return false;
1333                }
1334            }
1335        });
1336
1337        // List calls
1338        parser.register("+CLCC", new AtCommandHandler() {
1339            @Override
1340            public AtCommandResult handleActionCommand() {
1341                return getClccResult();
1342            }
1343        });
1344
1345        // Call Hold and Multiparty Handling command
1346        parser.register("+CHLD", new AtCommandHandler() {
1347            @Override
1348            public AtCommandResult handleSetCommand(Object[] args) {
1349                if (args.length >= 1) {
1350                    if (args[0].equals(0)) {
1351                        boolean result;
1352                        if (mRingingCall.isRinging()) {
1353                            result = PhoneUtils.hangupRingingCall(mPhone);
1354                        } else {
1355                            result = PhoneUtils.hangupHoldingCall(mPhone);
1356                        }
1357                        if (result) {
1358                            return new AtCommandResult(AtCommandResult.OK);
1359                        } else {
1360                            return new AtCommandResult(AtCommandResult.ERROR);
1361                        }
1362                    } else if (args[0].equals(1)) {
1363                        // Hangup active call, answer held call
1364                        if (PhoneUtils.answerAndEndActive(mPhone)) {
1365                            return new AtCommandResult(AtCommandResult.OK);
1366                        } else {
1367                            return new AtCommandResult(AtCommandResult.ERROR);
1368                        }
1369                    } else if (args[0].equals(2)) {
1370                        PhoneUtils.switchHoldingAndActive(mPhone);
1371                        return new AtCommandResult(AtCommandResult.OK);
1372                    } else if (args[0].equals(3)) {
1373                        if (mForegroundCall.getState().isAlive() &&
1374                            mBackgroundCall.getState().isAlive()) {
1375                            PhoneUtils.mergeCalls(mPhone);
1376                        }
1377                        return new AtCommandResult(AtCommandResult.OK);
1378                    }
1379                }
1380                return new AtCommandResult(AtCommandResult.ERROR);
1381            }
1382            @Override
1383            public AtCommandResult handleTestCommand() {
1384                return new AtCommandResult("+CHLD: (0,1,2,3)");
1385            }
1386        });
1387
1388        // Get Network operator name
1389        parser.register("+COPS", new AtCommandHandler() {
1390            @Override
1391            public AtCommandResult handleReadCommand() {
1392                String operatorName = mPhone.getServiceState().getOperatorAlphaLong();
1393                if (operatorName != null) {
1394                    if (operatorName.length() > 16) {
1395                        operatorName = operatorName.substring(0, 16);
1396                    }
1397                    return new AtCommandResult(
1398                            "+COPS: 0,0,\"" + operatorName + "\"");
1399                } else {
1400                    return new AtCommandResult(
1401                            "+COPS: 0,0,\"UNKNOWN\",0");
1402                }
1403            }
1404            @Override
1405            public AtCommandResult handleSetCommand(Object[] args) {
1406                // Handsfree only supports AT+COPS=3,0
1407                if (args.length != 2 || !(args[0] instanceof Integer)
1408                    || !(args[1] instanceof Integer)) {
1409                    // syntax error
1410                    return new AtCommandResult(AtCommandResult.ERROR);
1411                } else if ((Integer)args[0] != 3 || (Integer)args[1] != 0) {
1412                    return reportCmeError(BluetoothCmeError.OPERATION_NOT_SUPPORTED);
1413                } else {
1414                    return new AtCommandResult(AtCommandResult.OK);
1415                }
1416            }
1417            @Override
1418            public AtCommandResult handleTestCommand() {
1419                // Out of spec, but lets be friendly
1420                return new AtCommandResult("+COPS: (3),(0)");
1421            }
1422        });
1423
1424        // Mobile PIN
1425        // AT+CPIN is not in the handsfree spec (although it is in 3GPP)
1426        parser.register("+CPIN", new AtCommandHandler() {
1427            @Override
1428            public AtCommandResult handleReadCommand() {
1429                return new AtCommandResult("+CPIN: READY");
1430            }
1431        });
1432
1433        // Bluetooth Response and Hold
1434        // Only supported on PDC (Japan) and CDMA networks.
1435        parser.register("+BTRH", new AtCommandHandler() {
1436            @Override
1437            public AtCommandResult handleReadCommand() {
1438                // Replying with just OK indicates no response and hold
1439                // features in use now
1440                return new AtCommandResult(AtCommandResult.OK);
1441            }
1442            @Override
1443            public AtCommandResult handleSetCommand(Object[] args) {
1444                // Neeed PDC or CDMA
1445                return new AtCommandResult(AtCommandResult.ERROR);
1446            }
1447        });
1448
1449        // Request International Mobile Subscriber Identity (IMSI)
1450        // Not in bluetooth handset spec
1451        parser.register("+CIMI", new AtCommandHandler() {
1452            @Override
1453            public AtCommandResult handleActionCommand() {
1454                // AT+CIMI
1455                String imsi = mPhone.getSubscriberId();
1456                if (imsi == null || imsi.length() == 0) {
1457                    return reportCmeError(BluetoothCmeError.SIM_FAILURE);
1458                } else {
1459                    return new AtCommandResult(imsi);
1460                }
1461            }
1462        });
1463
1464        // Calling Line Identification Presentation
1465        parser.register("+CLIP", new AtCommandHandler() {
1466            @Override
1467            public AtCommandResult handleReadCommand() {
1468                // Currently assumes the network is provisioned for CLIP
1469                return new AtCommandResult("+CLIP: " + (mClip ? "1" : "0") + ",1");
1470            }
1471            @Override
1472            public AtCommandResult handleSetCommand(Object[] args) {
1473                // AT+CLIP=<n>
1474                if (args.length >= 1 && (args[0].equals(0) || args[0].equals(1))) {
1475                    mClip = args[0].equals(1);
1476                    return new AtCommandResult(AtCommandResult.OK);
1477                } else {
1478                    return new AtCommandResult(AtCommandResult.ERROR);
1479                }
1480            }
1481            @Override
1482            public AtCommandResult handleTestCommand() {
1483                return new AtCommandResult("+CLIP: (0-1)");
1484            }
1485        });
1486
1487        // AT+CGSN - Returns the device IMEI number.
1488        parser.register("+CGSN", new AtCommandHandler() {
1489            @Override
1490            public AtCommandResult handleActionCommand() {
1491                // Get the IMEI of the device.
1492                // mPhone will not be NULL at this point.
1493                return new AtCommandResult("+CGSN: " + mPhone.getDeviceId());
1494            }
1495        });
1496
1497        // AT+CGMM - Query Model Information
1498        parser.register("+CGMM", new AtCommandHandler() {
1499            @Override
1500            public AtCommandResult handleActionCommand() {
1501                // Return the Model Information.
1502                String model = SystemProperties.get("ro.product.model");
1503                if (model != null) {
1504                    return new AtCommandResult("+CGMM: " + model);
1505                } else {
1506                    return new AtCommandResult(AtCommandResult.ERROR);
1507                }
1508            }
1509        });
1510
1511        // AT+CGMI - Query Manufacturer Information
1512        parser.register("+CGMI", new AtCommandHandler() {
1513            @Override
1514            public AtCommandResult handleActionCommand() {
1515                // Return the Model Information.
1516                String manuf = SystemProperties.get("ro.product.manufacturer");
1517                if (manuf != null) {
1518                    return new AtCommandResult("+CGMI: " + manuf);
1519                } else {
1520                    return new AtCommandResult(AtCommandResult.ERROR);
1521                }
1522            }
1523        });
1524
1525        // Noise Reduction and Echo Cancellation control
1526        parser.register("+NREC", new AtCommandHandler() {
1527            @Override
1528            public AtCommandResult handleSetCommand(Object[] args) {
1529                if (args[0].equals(0)) {
1530                    mAudioManager.setParameter(HEADSET_NREC, "off");
1531                    return new AtCommandResult(AtCommandResult.OK);
1532                } else if (args[0].equals(1)) {
1533                    mAudioManager.setParameter(HEADSET_NREC, "on");
1534                    return new AtCommandResult(AtCommandResult.OK);
1535                }
1536                return new AtCommandResult(AtCommandResult.ERROR);
1537            }
1538        });
1539
1540        // Voice recognition (dialing)
1541        parser.register("+BVRA", new AtCommandHandler() {
1542            @Override
1543            public AtCommandResult handleSetCommand(Object[] args) {
1544                if (BluetoothHeadset.DISABLE_BT_VOICE_DIALING) {
1545                    return new AtCommandResult(AtCommandResult.ERROR);
1546                }
1547                if (args.length >= 1 && args[0].equals(1)) {
1548                    synchronized (BluetoothHandsfree.this) {
1549                        if (!mWaitingForVoiceRecognition) {
1550                            try {
1551                                mContext.startActivity(sVoiceCommandIntent);
1552                            } catch (ActivityNotFoundException e) {
1553                                return new AtCommandResult(AtCommandResult.ERROR);
1554                            }
1555                            expectVoiceRecognition();
1556                        }
1557                    }
1558                    return new AtCommandResult(AtCommandResult.UNSOLICITED);  // send nothing yet
1559                } else if (args.length >= 1 && args[0].equals(0)) {
1560                    audioOff();
1561                    return new AtCommandResult(AtCommandResult.OK);
1562                }
1563                return new AtCommandResult(AtCommandResult.ERROR);
1564            }
1565            @Override
1566            public AtCommandResult handleTestCommand() {
1567                return new AtCommandResult("+BVRA: (0-1)");
1568            }
1569        });
1570
1571        // Retrieve Subscriber Number
1572        parser.register("+CNUM", new AtCommandHandler() {
1573            @Override
1574            public AtCommandResult handleActionCommand() {
1575                String number = mPhone.getLine1Number();
1576                if (number == null) {
1577                    return new AtCommandResult(AtCommandResult.OK);
1578                }
1579                return new AtCommandResult("+CNUM: ,\"" + number + "\"," +
1580                        PhoneNumberUtils.toaFromString(number) + ",,4");
1581            }
1582        });
1583
1584        // Microphone Gain
1585        parser.register("+VGM", new AtCommandHandler() {
1586            @Override
1587            public AtCommandResult handleSetCommand(Object[] args) {
1588                // AT+VGM=<gain>    in range [0,15]
1589                // Headset/Handsfree is reporting its current gain setting
1590                return new AtCommandResult(AtCommandResult.OK);
1591            }
1592        });
1593
1594        // Speaker Gain
1595        parser.register("+VGS", new AtCommandHandler() {
1596            @Override
1597            public AtCommandResult handleSetCommand(Object[] args) {
1598                // AT+VGS=<gain>    in range [0,15]
1599                if (args.length != 1 || !(args[0] instanceof Integer)) {
1600                    return new AtCommandResult(AtCommandResult.ERROR);
1601                }
1602                mScoGain = (Integer) args[0];
1603                int flag =  mAudioManager.isBluetoothScoOn() ? AudioManager.FLAG_SHOW_UI:0;
1604
1605                mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, mScoGain, flag);
1606                return new AtCommandResult(AtCommandResult.OK);
1607            }
1608        });
1609
1610        // Phone activity status
1611        parser.register("+CPAS", new AtCommandHandler() {
1612            @Override
1613            public AtCommandResult handleActionCommand() {
1614                int status = 0;
1615                switch (mPhone.getState()) {
1616                case IDLE:
1617                    status = 0;
1618                    break;
1619                case RINGING:
1620                    status = 3;
1621                    break;
1622                case OFFHOOK:
1623                    status = 4;
1624                    break;
1625                }
1626                return new AtCommandResult("+CPAS: " + status);
1627            }
1628        });
1629        mPhonebook.register(parser);
1630    }
1631
1632    public void sendScoGainUpdate(int gain) {
1633        if (mScoGain != gain && (mRemoteBrsf & BRSF_HF_REMOTE_VOL_CONTROL) != 0x0) {
1634            sendURC("+VGS:" + gain);
1635            mScoGain = gain;
1636        }
1637    }
1638
1639    public AtCommandResult reportCmeError(int error) {
1640        if (mCmee) {
1641            AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
1642            result.addResponse("+CME ERROR: " + error);
1643            return result;
1644        } else {
1645            return new AtCommandResult(AtCommandResult.ERROR);
1646        }
1647    }
1648
1649    private static final int START_CALL_TIMEOUT = 10000;  // ms
1650
1651    private synchronized void expectCallStart() {
1652        mWaitingForCallStart = true;
1653        Message msg = Message.obtain(mHandler, CHECK_CALL_STARTED);
1654        mHandler.sendMessageDelayed(msg, START_CALL_TIMEOUT);
1655        if (!mStartCallWakeLock.isHeld()) {
1656            mStartCallWakeLock.acquire(START_CALL_TIMEOUT);
1657        }
1658    }
1659
1660    private synchronized void callStarted() {
1661        if (mWaitingForCallStart) {
1662            mWaitingForCallStart = false;
1663            sendURC("OK");
1664            if (mStartCallWakeLock.isHeld()) {
1665                mStartCallWakeLock.release();
1666            }
1667        }
1668    }
1669
1670    private static final int START_VOICE_RECOGNITION_TIMEOUT = 5000;  // ms
1671
1672    private synchronized void expectVoiceRecognition() {
1673        mWaitingForVoiceRecognition = true;
1674        Message msg = Message.obtain(mHandler, CHECK_VOICE_RECOGNITION_STARTED);
1675        mHandler.sendMessageDelayed(msg, START_VOICE_RECOGNITION_TIMEOUT);
1676        if (!mStartVoiceRecognitionWakeLock.isHeld()) {
1677            mStartVoiceRecognitionWakeLock.acquire(START_VOICE_RECOGNITION_TIMEOUT);
1678        }
1679    }
1680
1681    /* package */ synchronized boolean startVoiceRecognition() {
1682        if (mWaitingForVoiceRecognition) {
1683            // HF initiated
1684            mWaitingForVoiceRecognition = false;
1685            sendURC("OK");
1686        } else {
1687            // AG initiated
1688            sendURC("+BVRA: 1");
1689        }
1690        boolean ret = audioOn();
1691        if (mStartVoiceRecognitionWakeLock.isHeld()) {
1692            mStartVoiceRecognitionWakeLock.release();
1693        }
1694        return ret;
1695    }
1696
1697    /* package */ synchronized boolean stopVoiceRecognition() {
1698        sendURC("+BVRA: 0");
1699        audioOff();
1700        return true;
1701    }
1702
1703    private boolean inDebug() {
1704        return DBG && SystemProperties.getBoolean(DebugThread.DEBUG_HANDSFREE, false);
1705    }
1706
1707    private boolean allowAudioAnytime() {
1708        return inDebug() && SystemProperties.getBoolean(DebugThread.DEBUG_HANDSFREE_AUDIO_ANYTIME,
1709                false);
1710    }
1711
1712    private void startDebug() {
1713        if (DBG && mDebugThread == null) {
1714            mDebugThread = new DebugThread();
1715            mDebugThread.start();
1716        }
1717    }
1718
1719    private void stopDebug() {
1720        if (mDebugThread != null) {
1721            mDebugThread.interrupt();
1722            mDebugThread = null;
1723        }
1724    }
1725
1726    /** Debug thread to read debug properties - runs when debug.bt.hfp is true
1727     *  at the time a bluetooth handsfree device is connected. Debug properties
1728     *  are polled and mock updates sent every 1 second */
1729    private class DebugThread extends Thread {
1730        /** Turns on/off handsfree profile debugging mode */
1731        private static final String DEBUG_HANDSFREE = "debug.bt.hfp";
1732
1733        /** Mock battery level change - use 0 to 5 */
1734        private static final String DEBUG_HANDSFREE_BATTERY = "debug.bt.hfp.battery";
1735
1736        /** Mock no cellular service when false */
1737        private static final String DEBUG_HANDSFREE_SERVICE = "debug.bt.hfp.service";
1738
1739        /** Mock cellular roaming when true */
1740        private static final String DEBUG_HANDSFREE_ROAM = "debug.bt.hfp.roam";
1741
1742        /** false to true transition will force an audio (SCO) connection to
1743         *  be established. true to false will force audio to be disconnected
1744         */
1745        private static final String DEBUG_HANDSFREE_AUDIO = "debug.bt.hfp.audio";
1746
1747        /** true allows incoming SCO connection out of call.
1748         */
1749        private static final String DEBUG_HANDSFREE_AUDIO_ANYTIME = "debug.bt.hfp.audio_anytime";
1750
1751        /** Mock signal strength change in ASU - use 0 to 31 */
1752        private static final String DEBUG_HANDSFREE_SIGNAL = "debug.bt.hfp.signal";
1753
1754        /** Debug AT+CLCC: print +CLCC result */
1755        private static final String DEBUG_HANDSFREE_CLCC = "debug.bt.hfp.clcc";
1756
1757        /** Debug AT+BSIR - Send In Band Ringtones Unsolicited AT command.
1758         * debug.bt.unsol.inband = 0 => AT+BSIR = 0 sent by the AG
1759         * debug.bt.unsol.inband = 1 => AT+BSIR = 0 sent by the AG
1760         * Other values are ignored.
1761         */
1762
1763        private static final String DEBUG_UNSOL_INBAND_RINGTONE =
1764            "debug.bt.unsol.inband";
1765
1766        @Override
1767        public void run() {
1768            boolean oldService = true;
1769            boolean oldRoam = false;
1770            boolean oldAudio = false;
1771
1772            while (!isInterrupted() && inDebug()) {
1773                int batteryLevel = SystemProperties.getInt(DEBUG_HANDSFREE_BATTERY, -1);
1774                if (batteryLevel >= 0 && batteryLevel <= 5) {
1775                    Intent intent = new Intent();
1776                    intent.putExtra("level", batteryLevel);
1777                    intent.putExtra("scale", 5);
1778                    mPhoneState.updateBatteryState(intent);
1779                }
1780
1781                boolean serviceStateChanged = false;
1782                if (SystemProperties.getBoolean(DEBUG_HANDSFREE_SERVICE, true) != oldService) {
1783                    oldService = !oldService;
1784                    serviceStateChanged = true;
1785                }
1786                if (SystemProperties.getBoolean(DEBUG_HANDSFREE_ROAM, false) != oldRoam) {
1787                    oldRoam = !oldRoam;
1788                    serviceStateChanged = true;
1789                }
1790                if (serviceStateChanged) {
1791                    Bundle b = new Bundle();
1792                    b.putInt("state", oldService ? 0 : 1);
1793                    b.putBoolean("roaming", oldRoam);
1794                    mPhoneState.updateServiceState(true, ServiceState.newFromBundle(b));
1795                }
1796
1797                if (SystemProperties.getBoolean(DEBUG_HANDSFREE_AUDIO, false) != oldAudio) {
1798                    oldAudio = !oldAudio;
1799                    if (oldAudio) {
1800                        audioOn();
1801                    } else {
1802                        audioOff();
1803                    }
1804                }
1805
1806                int signalLevel = SystemProperties.getInt(DEBUG_HANDSFREE_SIGNAL, -1);
1807                if (signalLevel >= 0 && signalLevel <= 31) {
1808                    SignalStrength signalStrength = new SignalStrength(signalLevel, -1, -1, -1,
1809                            -1, -1, -1, true);
1810                    Intent intent = new Intent();
1811                    Bundle data = new Bundle();
1812                    signalStrength.fillInNotifierBundle(data);
1813                    intent.putExtras(data);
1814                    mPhoneState.updateSignalState(intent);
1815                }
1816
1817                if (SystemProperties.getBoolean(DEBUG_HANDSFREE_CLCC, false)) {
1818                    log(getClccResult().toString());
1819                }
1820                try {
1821                    sleep(1000);  // 1 second
1822                } catch (InterruptedException e) {
1823                    break;
1824                }
1825
1826                int inBandRing =
1827                    SystemProperties.getInt(DEBUG_UNSOL_INBAND_RINGTONE, -1);
1828                if (inBandRing == 0 || inBandRing == 1) {
1829                    AtCommandResult result =
1830                        new AtCommandResult(AtCommandResult.UNSOLICITED);
1831                    result.addResponse("+BSIR: " + inBandRing);
1832                    sendURC(result.toString());
1833                }
1834            }
1835        }
1836    }
1837
1838    private static void log(String msg) {
1839        Log.d(TAG, msg);
1840    }
1841}
1842
1843
1844
1845