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