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