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