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