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