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