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