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