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