• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.BluetoothA2dp;
23 import android.bluetooth.BluetoothAdapter;
24 import android.bluetooth.BluetoothDevice;
25 import android.bluetooth.BluetoothHeadset;
26 import android.bluetooth.HeadsetBase;
27 import android.bluetooth.ScoSocket;
28 import android.content.ActivityNotFoundException;
29 import android.content.BroadcastReceiver;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.media.AudioManager;
34 import android.net.Uri;
35 import android.os.AsyncResult;
36 import android.os.Bundle;
37 import android.os.Handler;
38 import android.os.Message;
39 import android.os.PowerManager;
40 import android.os.PowerManager.WakeLock;
41 import android.os.SystemProperties;
42 import android.telephony.PhoneNumberUtils;
43 import android.telephony.ServiceState;
44 import android.telephony.SignalStrength;
45 import android.util.Log;
46 
47 import com.android.internal.telephony.Call;
48 import com.android.internal.telephony.Connection;
49 import com.android.internal.telephony.Phone;
50 import com.android.internal.telephony.TelephonyIntents;
51 import com.android.internal.telephony.CallManager;
52 
53 import java.util.LinkedList;
54 
55 /**
56  * Bluetooth headset manager for the Phone app.
57  * @hide
58  */
59 public class BluetoothHandsfree {
60     private static final String TAG = "BT HS/HF";
61     private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 1)
62             && (SystemProperties.getInt("ro.debuggable", 0) == 1);
63     private static final boolean VDBG = (PhoneApp.DBG_LEVEL >= 2);  // even more logging
64 
65     public static final int TYPE_UNKNOWN           = 0;
66     public static final int TYPE_HEADSET           = 1;
67     public static final int TYPE_HANDSFREE         = 2;
68 
69     private final Context mContext;
70     private final CallManager mCM;
71     private final BluetoothA2dp mA2dp;
72 
73     private BluetoothDevice mA2dpDevice;
74     private int mA2dpState;
75 
76     private ServiceState mServiceState;
77     private HeadsetBase mHeadset;  // null when not connected
78     private int mHeadsetType;
79     private boolean mAudioPossible;
80     private ScoSocket mIncomingSco;
81     private ScoSocket mOutgoingSco;
82     private ScoSocket mConnectedSco;
83 
84     private AudioManager mAudioManager;
85     private PowerManager mPowerManager;
86 
87     private boolean mPendingSco;  // waiting for a2dp sink to suspend before establishing SCO
88     private boolean mA2dpSuspended;
89     private boolean mUserWantsAudio;
90     private WakeLock mStartCallWakeLock;  // held while waiting for the intent to start call
91     private WakeLock mStartVoiceRecognitionWakeLock;  // held while waiting for voice recognition
92 
93     // AT command state
94     private static final int GSM_MAX_CONNECTIONS = 6;  // Max connections allowed by GSM
95     private static final int CDMA_MAX_CONNECTIONS = 2;  // Max connections allowed by CDMA
96 
97     private long mBgndEarliestConnectionTime = 0;
98     private boolean mClip = false;  // Calling Line Information Presentation
99     private boolean mIndicatorsEnabled = false;
100     private boolean mCmee = false;  // Extended Error reporting
101     private long[] mClccTimestamps; // Timestamps associated with each clcc index
102     private boolean[] mClccUsed;     // Is this clcc index in use
103     private boolean mWaitingForCallStart;
104     private boolean mWaitingForVoiceRecognition;
105     // do not connect audio until service connection is established
106     // for 3-way supported devices, this is after AT+CHLD
107     // for non-3-way supported devices, this is after AT+CMER (see spec)
108     private boolean mServiceConnectionEstablished;
109 
110     private final BluetoothPhoneState mBluetoothPhoneState;  // for CIND and CIEV updates
111     private final BluetoothAtPhonebook mPhonebook;
112     private Phone.State mPhoneState = Phone.State.IDLE;
113     CdmaPhoneCallState.PhoneCallState mCdmaThreeWayCallState =
114                                             CdmaPhoneCallState.PhoneCallState.IDLE;
115 
116     private DebugThread mDebugThread;
117     private int mScoGain = Integer.MIN_VALUE;
118 
119     private static Intent sVoiceCommandIntent;
120 
121     // Audio parameters
122     private static final String HEADSET_NREC = "bt_headset_nrec";
123     private static final String HEADSET_NAME = "bt_headset_name";
124 
125     private int mRemoteBrsf = 0;
126     private int mLocalBrsf = 0;
127 
128     // CDMA specific flag used in context with BT devices having display capabilities
129     // to show which Caller is active. This state might not be always true as in CDMA
130     // networks if a caller drops off no update is provided to the Phone.
131     // This flag is just used as a toggle to provide a update to the BT device to specify
132     // which caller is active.
133     private boolean mCdmaIsSecondCallActive = false;
134     private boolean mCdmaCallsSwapped = false;
135 
136     /* Constants from Bluetooth Specification Hands-Free profile version 1.5 */
137     private static final int BRSF_AG_THREE_WAY_CALLING = 1 << 0;
138     private static final int BRSF_AG_EC_NR = 1 << 1;
139     private static final int BRSF_AG_VOICE_RECOG = 1 << 2;
140     private static final int BRSF_AG_IN_BAND_RING = 1 << 3;
141     private static final int BRSF_AG_VOICE_TAG_NUMBE = 1 << 4;
142     private static final int BRSF_AG_REJECT_CALL = 1 << 5;
143     private static final int BRSF_AG_ENHANCED_CALL_STATUS = 1 <<  6;
144     private static final int BRSF_AG_ENHANCED_CALL_CONTROL = 1 << 7;
145     private static final int BRSF_AG_ENHANCED_ERR_RESULT_CODES = 1 << 8;
146 
147     private static final int BRSF_HF_EC_NR = 1 << 0;
148     private static final int BRSF_HF_CW_THREE_WAY_CALLING = 1 << 1;
149     private static final int BRSF_HF_CLIP = 1 << 2;
150     private static final int BRSF_HF_VOICE_REG_ACT = 1 << 3;
151     private static final int BRSF_HF_REMOTE_VOL_CONTROL = 1 << 4;
152     private static final int BRSF_HF_ENHANCED_CALL_STATUS = 1 <<  5;
153     private static final int BRSF_HF_ENHANCED_CALL_CONTROL = 1 << 6;
154 
typeToString(int type)155     public static String typeToString(int type) {
156         switch (type) {
157         case TYPE_UNKNOWN:
158             return "unknown";
159         case TYPE_HEADSET:
160             return "headset";
161         case TYPE_HANDSFREE:
162             return "handsfree";
163         }
164         return null;
165     }
166 
BluetoothHandsfree(Context context, CallManager cm)167     public BluetoothHandsfree(Context context, CallManager cm) {
168         mCM = cm;
169         mContext = context;
170         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
171         boolean bluetoothCapable = (adapter != null);
172         mHeadset = null;  // nothing connected yet
173         mA2dp = new BluetoothA2dp(mContext);
174         mA2dpState = BluetoothA2dp.STATE_DISCONNECTED;
175         mA2dpDevice = null;
176         mA2dpSuspended = false;
177 
178         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
179         mStartCallWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
180                                                        TAG + ":StartCall");
181         mStartCallWakeLock.setReferenceCounted(false);
182         mStartVoiceRecognitionWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
183                                                        TAG + ":VoiceRecognition");
184         mStartVoiceRecognitionWakeLock.setReferenceCounted(false);
185 
186         mLocalBrsf = BRSF_AG_THREE_WAY_CALLING |
187                      BRSF_AG_EC_NR |
188                      BRSF_AG_REJECT_CALL |
189                      BRSF_AG_ENHANCED_CALL_STATUS;
190 
191         if (sVoiceCommandIntent == null) {
192             sVoiceCommandIntent = new Intent(Intent.ACTION_VOICE_COMMAND);
193             sVoiceCommandIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
194         }
195         if (mContext.getPackageManager().resolveActivity(sVoiceCommandIntent, 0) != null &&
196                 BluetoothHeadset.isBluetoothVoiceDialingEnabled(mContext)) {
197             mLocalBrsf |= BRSF_AG_VOICE_RECOG;
198         }
199 
200         mBluetoothPhoneState = new BluetoothPhoneState();
201         mUserWantsAudio = true;
202         mPhonebook = new BluetoothAtPhonebook(mContext, this);
203         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
204         cdmaSetSecondCallState(false);
205 
206         if (bluetoothCapable) {
207             resetAtState();
208         }
209 
210     }
211 
onBluetoothEnabled()212     /* package */ synchronized void onBluetoothEnabled() {
213         /* Bluez has a bug where it will always accept and then orphan
214          * incoming SCO connections, regardless of whether we have a listening
215          * SCO socket. So the best thing to do is always run a listening socket
216          * while bluetooth is on so that at least we can diconnect it
217          * immediately when we don't want it.
218          */
219         if (mIncomingSco == null) {
220             mIncomingSco = createScoSocket();
221             mIncomingSco.accept();
222         }
223     }
224 
onBluetoothDisabled()225     /* package */ synchronized void onBluetoothDisabled() {
226         audioOff();
227         if (mIncomingSco != null) {
228             mIncomingSco.close();
229             mIncomingSco = null;
230         }
231     }
232 
isHeadsetConnected()233     private boolean isHeadsetConnected() {
234         if (mHeadset == null) {
235             return false;
236         }
237         return mHeadset.isConnected();
238     }
239 
connectHeadset(HeadsetBase headset, int headsetType)240     /* package */ synchronized void connectHeadset(HeadsetBase headset, int headsetType) {
241         mHeadset = headset;
242         mHeadsetType = headsetType;
243         if (mHeadsetType == TYPE_HEADSET) {
244             initializeHeadsetAtParser();
245         } else {
246             initializeHandsfreeAtParser();
247         }
248         headset.startEventThread();
249         configAudioParameters();
250 
251         if (inDebug()) {
252             startDebug();
253         }
254 
255         if (isIncallAudio()) {
256             audioOn();
257         }
258     }
259 
260     /* returns true if there is some kind of in-call audio we may wish to route
261      * bluetooth to */
isIncallAudio()262     private boolean isIncallAudio() {
263         Call.State state = mCM.getActiveFgCallState();
264 
265         return (state == Call.State.ACTIVE || state == Call.State.ALERTING);
266     }
267 
disconnectHeadset()268     /* package */ synchronized void disconnectHeadset() {
269         // Close off the SCO sockets
270         audioOff();
271         mHeadset = null;
272         stopDebug();
273         resetAtState();
274     }
275 
resetAtState()276     /* package */ synchronized void resetAtState() {
277         mClip = false;
278         mIndicatorsEnabled = false;
279         mServiceConnectionEstablished = false;
280         mCmee = false;
281         mClccTimestamps = new long[GSM_MAX_CONNECTIONS];
282         mClccUsed = new boolean[GSM_MAX_CONNECTIONS];
283         for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
284             mClccUsed[i] = false;
285         }
286         mRemoteBrsf = 0;
287         mPhonebook.resetAtState();
288     }
289 
configAudioParameters()290     private void configAudioParameters() {
291         String name = mHeadset.getRemoteDevice().getName();
292         if (name == null) {
293             name = "<unknown>";
294         }
295         mAudioManager.setParameters(HEADSET_NAME+"="+name+";"+HEADSET_NREC+"=on");
296     }
297 
298 
299     /** Represents the data that we send in a +CIND or +CIEV command to the HF
300      */
301     private class BluetoothPhoneState {
302         // 0: no service
303         // 1: service
304         private int mService;
305 
306         // 0: no active call
307         // 1: active call (where active means audio is routed - not held call)
308         private int mCall;
309 
310         // 0: not in call setup
311         // 1: incoming call setup
312         // 2: outgoing call setup
313         // 3: remote party being alerted in an outgoing call setup
314         private int mCallsetup;
315 
316         // 0: no calls held
317         // 1: held call and active call
318         // 2: held call only
319         private int mCallheld;
320 
321         // cellular signal strength of AG: 0-5
322         private int mSignal;
323 
324         // cellular signal strength in CSQ rssi scale
325         private int mRssi;  // for CSQ
326 
327         // 0: roaming not active (home)
328         // 1: roaming active
329         private int mRoam;
330 
331         // battery charge of AG: 0-5
332         private int mBattchg;
333 
334         // 0: not registered
335         // 1: registered, home network
336         // 5: registered, roaming
337         private int mStat;  // for CREG
338 
339         private String mRingingNumber;  // Context for in-progress RING's
340         private int    mRingingType;
341         private boolean mIgnoreRing = false;
342         private boolean mStopRing = false;
343 
344         private static final int SERVICE_STATE_CHANGED = 1;
345         private static final int PRECISE_CALL_STATE_CHANGED = 2;
346         private static final int RING = 3;
347         private static final int PHONE_CDMA_CALL_WAITING = 4;
348 
349         private Handler mStateChangeHandler = new Handler() {
350             @Override
351             public void handleMessage(Message msg) {
352                 switch(msg.what) {
353                 case RING:
354                     AtCommandResult result = ring();
355                     if (result != null) {
356                         sendURC(result.toString());
357                     }
358                     break;
359                 case SERVICE_STATE_CHANGED:
360                     ServiceState state = (ServiceState) ((AsyncResult) msg.obj).result;
361                     updateServiceState(sendUpdate(), state);
362                     break;
363                 case PRECISE_CALL_STATE_CHANGED:
364                 case PHONE_CDMA_CALL_WAITING:
365                     Connection connection = null;
366                     if (((AsyncResult) msg.obj).result instanceof Connection) {
367                         connection = (Connection) ((AsyncResult) msg.obj).result;
368                     }
369                     handlePreciseCallStateChange(sendUpdate(), connection);
370                     break;
371                 }
372             }
373         };
374 
BluetoothPhoneState()375         private BluetoothPhoneState() {
376             // init members
377             // TODO May consider to repalce the default phone's state and signal
378             //      by CallManagter's state and signal
379             updateServiceState(false, mCM.getDefaultPhone().getServiceState());
380             handlePreciseCallStateChange(false, null);
381             mBattchg = 5;  // There is currently no API to get battery level
382                            // on demand, so set to 5 and wait for an update
383             mSignal = asuToSignal(mCM.getDefaultPhone().getSignalStrength());
384 
385             // register for updates
386             // Use the service state of default phone as BT service state to
387             // avoid situation such as no cell or wifi connection but still
388             // reporting in service (since SipPhone always reports in service).
389             mCM.getDefaultPhone().registerForServiceStateChanged(mStateChangeHandler,
390                                                   SERVICE_STATE_CHANGED, null);
391             mCM.registerForPreciseCallStateChanged(mStateChangeHandler,
392                     PRECISE_CALL_STATE_CHANGED, null);
393             mCM.registerForCallWaiting(mStateChangeHandler,
394                 PHONE_CDMA_CALL_WAITING, null);
395 
396             IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
397             filter.addAction(TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED);
398             filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
399             mContext.registerReceiver(mStateReceiver, filter);
400         }
401 
updateBtPhoneStateAfterRadioTechnologyChange()402         private void updateBtPhoneStateAfterRadioTechnologyChange() {
403             if(VDBG) Log.d(TAG, "updateBtPhoneStateAfterRadioTechnologyChange...");
404 
405             //Unregister all events from the old obsolete phone
406             mCM.getDefaultPhone().unregisterForServiceStateChanged(mStateChangeHandler);
407             mCM.unregisterForPreciseCallStateChanged(mStateChangeHandler);
408             mCM.unregisterForCallWaiting(mStateChangeHandler);
409 
410             //Register all events new to the new active phone
411             mCM.getDefaultPhone().registerForServiceStateChanged(mStateChangeHandler,
412                                                   SERVICE_STATE_CHANGED, null);
413             mCM.registerForPreciseCallStateChanged(mStateChangeHandler,
414                     PRECISE_CALL_STATE_CHANGED, null);
415             mCM.registerForCallWaiting(mStateChangeHandler,
416                 PHONE_CDMA_CALL_WAITING, null);
417         }
418 
sendUpdate()419         private boolean sendUpdate() {
420             return isHeadsetConnected() && mHeadsetType == TYPE_HANDSFREE && mIndicatorsEnabled;
421         }
422 
sendClipUpdate()423         private boolean sendClipUpdate() {
424             return isHeadsetConnected() && mHeadsetType == TYPE_HANDSFREE && mClip;
425         }
426 
stopRing()427         private void stopRing() {
428             mStopRing = true;
429         }
430 
431         /* convert [0,31] ASU signal strength to the [0,5] expected by
432          * bluetooth devices. Scale is similar to status bar policy
433          */
gsmAsuToSignal(SignalStrength signalStrength)434         private int gsmAsuToSignal(SignalStrength signalStrength) {
435             int asu = signalStrength.getGsmSignalStrength();
436             if      (asu >= 16) return 5;
437             else if (asu >= 8)  return 4;
438             else if (asu >= 4)  return 3;
439             else if (asu >= 2)  return 2;
440             else if (asu >= 1)  return 1;
441             else                return 0;
442         }
443 
444         /**
445          * Convert the cdma / evdo db levels to appropriate icon level.
446          * The scale is similar to the one used in status bar policy.
447          *
448          * @param signalStrength
449          * @return the icon level
450          */
cdmaDbmEcioToSignal(SignalStrength signalStrength)451         private int cdmaDbmEcioToSignal(SignalStrength signalStrength) {
452             int levelDbm = 0;
453             int levelEcio = 0;
454             int cdmaIconLevel = 0;
455             int evdoIconLevel = 0;
456             int cdmaDbm = signalStrength.getCdmaDbm();
457             int cdmaEcio = signalStrength.getCdmaEcio();
458 
459             if (cdmaDbm >= -75) levelDbm = 4;
460             else if (cdmaDbm >= -85) levelDbm = 3;
461             else if (cdmaDbm >= -95) levelDbm = 2;
462             else if (cdmaDbm >= -100) levelDbm = 1;
463             else levelDbm = 0;
464 
465             // Ec/Io are in dB*10
466             if (cdmaEcio >= -90) levelEcio = 4;
467             else if (cdmaEcio >= -110) levelEcio = 3;
468             else if (cdmaEcio >= -130) levelEcio = 2;
469             else if (cdmaEcio >= -150) levelEcio = 1;
470             else levelEcio = 0;
471 
472             cdmaIconLevel = (levelDbm < levelEcio) ? levelDbm : levelEcio;
473 
474             if (mServiceState != null &&
475                   (mServiceState.getRadioTechnology() == ServiceState.RADIO_TECHNOLOGY_EVDO_0 ||
476                    mServiceState.getRadioTechnology() == ServiceState.RADIO_TECHNOLOGY_EVDO_A)) {
477                   int evdoEcio = signalStrength.getEvdoEcio();
478                   int evdoSnr = signalStrength.getEvdoSnr();
479                   int levelEvdoEcio = 0;
480                   int levelEvdoSnr = 0;
481 
482                   // Ec/Io are in dB*10
483                   if (evdoEcio >= -650) levelEvdoEcio = 4;
484                   else if (evdoEcio >= -750) levelEvdoEcio = 3;
485                   else if (evdoEcio >= -900) levelEvdoEcio = 2;
486                   else if (evdoEcio >= -1050) levelEvdoEcio = 1;
487                   else levelEvdoEcio = 0;
488 
489                   if (evdoSnr > 7) levelEvdoSnr = 4;
490                   else if (evdoSnr > 5) levelEvdoSnr = 3;
491                   else if (evdoSnr > 3) levelEvdoSnr = 2;
492                   else if (evdoSnr > 1) levelEvdoSnr = 1;
493                   else levelEvdoSnr = 0;
494 
495                   evdoIconLevel = (levelEvdoEcio < levelEvdoSnr) ? levelEvdoEcio : levelEvdoSnr;
496             }
497             // TODO(): There is a bug open regarding what should be sent.
498             return (cdmaIconLevel > evdoIconLevel) ?  cdmaIconLevel : evdoIconLevel;
499 
500         }
501 
502 
asuToSignal(SignalStrength signalStrength)503         private int asuToSignal(SignalStrength signalStrength) {
504             if (signalStrength.isGsm()) {
505                 return gsmAsuToSignal(signalStrength);
506             } else {
507                 return cdmaDbmEcioToSignal(signalStrength);
508             }
509         }
510 
511 
512         /* convert [0,5] signal strength to a rssi signal strength for CSQ
513          * which is [0,31]. Despite the same scale, this is not the same value
514          * as ASU.
515          */
signalToRssi(int signal)516         private int signalToRssi(int signal) {
517             // using C4A suggested values
518             switch (signal) {
519             case 0: return 0;
520             case 1: return 4;
521             case 2: return 8;
522             case 3: return 13;
523             case 4: return 19;
524             case 5: return 31;
525             }
526             return 0;
527         }
528 
529 
530         private final BroadcastReceiver mStateReceiver = new BroadcastReceiver() {
531             @Override
532             public void onReceive(Context context, Intent intent) {
533                 if (intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) {
534                     updateBatteryState(intent);
535                 } else if (intent.getAction().equals(
536                             TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED)) {
537                     updateSignalState(intent);
538                 } else if (intent.getAction().equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) {
539                     int state = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE,
540                             BluetoothA2dp.STATE_DISCONNECTED);
541                     int oldState = intent.getIntExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE,
542                             BluetoothA2dp.STATE_DISCONNECTED);
543                     BluetoothDevice device =
544                             intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
545 
546                     // We are only concerned about Connected sinks to suspend and resume
547                     // them. We can safely ignore SINK_STATE_CHANGE for other devices.
548                     if (mA2dpDevice != null && !device.equals(mA2dpDevice)) return;
549 
550                     synchronized (BluetoothHandsfree.this) {
551                         mA2dpState = state;
552                         if (state == BluetoothA2dp.STATE_DISCONNECTED) {
553                             mA2dpDevice = null;
554                         } else {
555                             mA2dpDevice = device;
556                         }
557                         if (oldState == BluetoothA2dp.STATE_PLAYING &&
558                             mA2dpState == BluetoothA2dp.STATE_CONNECTED) {
559                             if (mA2dpSuspended) {
560                                 if (mPendingSco) {
561                                     mHandler.removeMessages(MESSAGE_CHECK_PENDING_SCO);
562                                     if (DBG) log("A2DP suspended, completing SCO");
563                                     mOutgoingSco = createScoSocket();
564                                     if (!mOutgoingSco.connect(
565                                             mHeadset.getRemoteDevice().getAddress(),
566                                             mHeadset.getRemoteDevice().getName())) {
567                                         mOutgoingSco = null;
568                                     }
569                                     mPendingSco = false;
570                                 }
571                             }
572                         }
573                     }
574                 }
575             }
576         };
577 
updateBatteryState(Intent intent)578         private synchronized void updateBatteryState(Intent intent) {
579             int batteryLevel = intent.getIntExtra("level", -1);
580             int scale = intent.getIntExtra("scale", -1);
581             if (batteryLevel == -1 || scale == -1) {
582                 return;  // ignore
583             }
584             batteryLevel = batteryLevel * 5 / scale;
585             if (mBattchg != batteryLevel) {
586                 mBattchg = batteryLevel;
587                 if (sendUpdate()) {
588                     sendURC("+CIEV: 7," + mBattchg);
589                 }
590             }
591         }
592 
updateSignalState(Intent intent)593         private synchronized void updateSignalState(Intent intent) {
594             // NOTE this function is called by the BroadcastReceiver mStateReceiver after intent
595             // ACTION_SIGNAL_STRENGTH_CHANGED and by the DebugThread mDebugThread
596             if (mHeadset == null) {
597                 return;
598             }
599 
600             SignalStrength signalStrength = SignalStrength.newFromBundle(intent.getExtras());
601             int signal;
602 
603             if (signalStrength != null) {
604                 signal = asuToSignal(signalStrength);
605                 mRssi = signalToRssi(signal);  // no unsolicited CSQ
606                 if (signal != mSignal) {
607                     mSignal = signal;
608                     if (sendUpdate()) {
609                         sendURC("+CIEV: 5," + mSignal);
610                     }
611                 }
612             } else {
613                 Log.e(TAG, "Signal Strength null");
614             }
615         }
616 
updateServiceState(boolean sendUpdate, ServiceState state)617         private synchronized void updateServiceState(boolean sendUpdate, ServiceState state) {
618             int service = state.getState() == ServiceState.STATE_IN_SERVICE ? 1 : 0;
619             int roam = state.getRoaming() ? 1 : 0;
620             int stat;
621             AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
622             mServiceState = state;
623             if (service == 0) {
624                 stat = 0;
625             } else {
626                 stat = (roam == 1) ? 5 : 1;
627             }
628 
629             if (service != mService) {
630                 mService = service;
631                 if (sendUpdate) {
632                     result.addResponse("+CIEV: 1," + mService);
633                 }
634             }
635             if (roam != mRoam) {
636                 mRoam = roam;
637                 if (sendUpdate) {
638                     result.addResponse("+CIEV: 6," + mRoam);
639                 }
640             }
641             if (stat != mStat) {
642                 mStat = stat;
643                 if (sendUpdate) {
644                     result.addResponse(toCregString());
645                 }
646             }
647 
648             sendURC(result.toString());
649         }
650 
handlePreciseCallStateChange(boolean sendUpdate, Connection connection)651         private synchronized void handlePreciseCallStateChange(boolean sendUpdate,
652                 Connection connection) {
653             int call = 0;
654             int callsetup = 0;
655             int callheld = 0;
656             int prevCallsetup = mCallsetup;
657             AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
658             Call foregroundCall = mCM.getActiveFgCall();
659             Call backgroundCall = mCM.getFirstActiveBgCall();
660             Call ringingCall = mCM.getFirstActiveRingingCall();
661 
662             if (VDBG) log("updatePhoneState()");
663 
664             // This function will get called when the Precise Call State
665             // {@link Call.State} changes. Hence, we might get this update
666             // even if the {@link Phone.state} is same as before.
667             // Check for the same.
668 
669             Phone.State newState = mCM.getState();
670             if (newState != mPhoneState) {
671                 mPhoneState = newState;
672                 switch (mPhoneState) {
673                 case IDLE:
674                     mUserWantsAudio = true;  // out of call - reset state
675                     audioOff();
676                     break;
677                 default:
678                     callStarted();
679                 }
680             }
681 
682             switch(foregroundCall.getState()) {
683             case ACTIVE:
684                 call = 1;
685                 mAudioPossible = true;
686                 break;
687             case DIALING:
688                 callsetup = 2;
689                 mAudioPossible = true;
690                 // We also need to send a Call started indication
691                 // for cases where the 2nd MO was initiated was
692                 // from a *BT hands free* and is waiting for a
693                 // +BLND: OK response
694                 // There is a special case handling of the same case
695                 // for CDMA below
696                 if (mCM.getFgPhone().getPhoneType() == Phone.PHONE_TYPE_GSM) {
697                     callStarted();
698                 }
699                 break;
700             case ALERTING:
701                 callsetup = 3;
702                 // Open the SCO channel for the outgoing call.
703                 audioOn();
704                 mAudioPossible = true;
705                 break;
706             case DISCONNECTING:
707                 // This is a transient state, we don't want to send
708                 // any AT commands during this state.
709                 call = mCall;
710                 callsetup = mCallsetup;
711                 callheld = mCallheld;
712                 break;
713             default:
714                 mAudioPossible = false;
715             }
716 
717             switch(ringingCall.getState()) {
718             case INCOMING:
719             case WAITING:
720                 callsetup = 1;
721                 break;
722             case DISCONNECTING:
723                 // This is a transient state, we don't want to send
724                 // any AT commands during this state.
725                 call = mCall;
726                 callsetup = mCallsetup;
727                 callheld = mCallheld;
728                 break;
729             }
730 
731             switch(backgroundCall.getState()) {
732             case HOLDING:
733                 if (call == 1) {
734                     callheld = 1;
735                 } else {
736                     call = 1;
737                     callheld = 2;
738                 }
739                 break;
740             case DISCONNECTING:
741                 // This is a transient state, we don't want to send
742                 // any AT commands during this state.
743                 call = mCall;
744                 callsetup = mCallsetup;
745                 callheld = mCallheld;
746                 break;
747             }
748 
749             if (mCall != call) {
750                 if (call == 1) {
751                     // This means that a call has transitioned from NOT ACTIVE to ACTIVE.
752                     // Switch on audio.
753                     audioOn();
754                 }
755                 mCall = call;
756                 if (sendUpdate) {
757                     result.addResponse("+CIEV: 2," + mCall);
758                 }
759             }
760             if (mCallsetup != callsetup) {
761                 mCallsetup = callsetup;
762                 if (sendUpdate) {
763                     // If mCall = 0, send CIEV
764                     // mCall = 1, mCallsetup = 0, send CIEV
765                     // mCall = 1, mCallsetup = 1, send CIEV after CCWA,
766                     // if 3 way supported.
767                     // mCall = 1, mCallsetup = 2 / 3 -> send CIEV,
768                     // if 3 way is supported
769                     if (mCall != 1 || mCallsetup == 0 ||
770                         mCallsetup != 1 && (mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) {
771                         result.addResponse("+CIEV: 3," + mCallsetup);
772                     }
773                 }
774             }
775 
776             if (mCM.getDefaultPhone().getPhoneType() == Phone.PHONE_TYPE_CDMA) {
777                 PhoneApp app = PhoneApp.getInstance();
778                 if (app.cdmaPhoneCallState != null) {
779                     CdmaPhoneCallState.PhoneCallState currCdmaThreeWayCallState =
780                             app.cdmaPhoneCallState.getCurrentCallState();
781                     CdmaPhoneCallState.PhoneCallState prevCdmaThreeWayCallState =
782                         app.cdmaPhoneCallState.getPreviousCallState();
783 
784                     log("CDMA call state: " + currCdmaThreeWayCallState + " prev state:" +
785                         prevCdmaThreeWayCallState);
786                     callheld = getCdmaCallHeldStatus(currCdmaThreeWayCallState,
787                                                      prevCdmaThreeWayCallState);
788 
789                     if (mCdmaThreeWayCallState != currCdmaThreeWayCallState) {
790                         // In CDMA, the network does not provide any feedback
791                         // to the phone when the 2nd MO call goes through the
792                         // stages of DIALING > ALERTING -> ACTIVE we fake the
793                         // sequence
794                         if ((currCdmaThreeWayCallState ==
795                                 CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
796                                     && app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing()) {
797                             mAudioPossible = true;
798                             if (sendUpdate) {
799                                 if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) {
800                                     result.addResponse("+CIEV: 3,2");
801                                     // Mimic putting the call on hold
802                                     result.addResponse("+CIEV: 4,1");
803                                     mCallheld = callheld;
804                                     result.addResponse("+CIEV: 3,3");
805                                     result.addResponse("+CIEV: 3,0");
806                                 }
807                             }
808                             // We also need to send a Call started indication
809                             // for cases where the 2nd MO was initiated was
810                             // from a *BT hands free* and is waiting for a
811                             // +BLND: OK response
812                             callStarted();
813                         }
814 
815                         // In CDMA, the network does not provide any feedback to
816                         // the phone when a user merges a 3way call or swaps
817                         // between two calls we need to send a CIEV response
818                         // indicating that a call state got changed which should
819                         // trigger a CLCC update request from the BT client.
820                         if (currCdmaThreeWayCallState ==
821                                 CdmaPhoneCallState.PhoneCallState.CONF_CALL &&
822                                 prevCdmaThreeWayCallState ==
823                                   CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
824                             mAudioPossible = true;
825                             if (sendUpdate) {
826                                 if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) {
827                                     result.addResponse("+CIEV: 2,1");
828                                     result.addResponse("+CIEV: 3,0");
829                                 }
830                             }
831                         }
832                     }
833                     mCdmaThreeWayCallState = currCdmaThreeWayCallState;
834                 }
835             }
836 
837             boolean callsSwitched;
838 
839             if (mCM.getDefaultPhone().getPhoneType() == Phone.PHONE_TYPE_CDMA &&
840                 mCdmaThreeWayCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
841                 callsSwitched = mCdmaCallsSwapped;
842             } else {
843                 callsSwitched =
844                     (callheld == 1 && ! (backgroundCall.getEarliestConnectTime() ==
845                         mBgndEarliestConnectionTime));
846                 mBgndEarliestConnectionTime = backgroundCall.getEarliestConnectTime();
847             }
848 
849 
850             if (mCallheld != callheld || callsSwitched) {
851                 mCallheld = callheld;
852                 if (sendUpdate) {
853                     result.addResponse("+CIEV: 4," + mCallheld);
854                 }
855             }
856 
857             if (callsetup == 1 && callsetup != prevCallsetup) {
858                 // new incoming call
859                 String number = null;
860                 int type = 128;
861                 // find incoming phone number and type
862                 if (connection == null) {
863                     connection = ringingCall.getEarliestConnection();
864                     if (connection == null) {
865                         Log.e(TAG, "Could not get a handle on Connection object for new " +
866                               "incoming call");
867                     }
868                 }
869                 if (connection != null) {
870                     number = connection.getAddress();
871                     if (number != null) {
872                         type = PhoneNumberUtils.toaFromString(number);
873                     }
874                 }
875                 if (number == null) {
876                     number = "";
877                 }
878                 if ((call != 0 || callheld != 0) && sendUpdate) {
879                     // call waiting
880                     if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) {
881                         result.addResponse("+CCWA: \"" + number + "\"," + type);
882                         result.addResponse("+CIEV: 3," + callsetup);
883                     }
884                 } else {
885                     // regular new incoming call
886                     mRingingNumber = number;
887                     mRingingType = type;
888                     mIgnoreRing = false;
889                     mStopRing = false;
890 
891                     if ((mLocalBrsf & BRSF_AG_IN_BAND_RING) != 0x0) {
892                         audioOn();
893                     }
894                     result.addResult(ring());
895                 }
896             }
897             sendURC(result.toString());
898         }
899 
getCdmaCallHeldStatus(CdmaPhoneCallState.PhoneCallState currState, CdmaPhoneCallState.PhoneCallState prevState)900         private int getCdmaCallHeldStatus(CdmaPhoneCallState.PhoneCallState currState,
901                                   CdmaPhoneCallState.PhoneCallState prevState) {
902             int callheld;
903             // Update the Call held information
904             if (currState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
905                 if (prevState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
906                     callheld = 0; //0: no calls held, as now *both* the caller are active
907                 } else {
908                     callheld = 1; //1: held call and active call, as on answering a
909                             // Call Waiting, one of the caller *is* put on hold
910                 }
911             } else if (currState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
912                 callheld = 1; //1: held call and active call, as on make a 3 Way Call
913                         // the first caller *is* put on hold
914             } else {
915                 callheld = 0; //0: no calls held as this is a SINGLE_ACTIVE call
916             }
917             return callheld;
918         }
919 
920 
ring()921         private AtCommandResult ring() {
922             if (!mIgnoreRing && !mStopRing && mCM.getFirstActiveRingingCall().isRinging()) {
923                 AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
924                 result.addResponse("RING");
925                 if (sendClipUpdate()) {
926                     result.addResponse("+CLIP: \"" + mRingingNumber + "\"," + mRingingType);
927                 }
928 
929                 Message msg = mStateChangeHandler.obtainMessage(RING);
930                 mStateChangeHandler.sendMessageDelayed(msg, 3000);
931                 return result;
932             }
933             return null;
934         }
935 
toCregString()936         private synchronized String toCregString() {
937             return new String("+CREG: 1," + mStat);
938         }
939 
updateCallHeld()940         private synchronized void updateCallHeld() {
941             if (mCallheld != 0) {
942                 mCallheld = 0;
943                 sendURC("+CIEV: 4,0");
944             }
945         }
946 
toCindResult()947         private synchronized AtCommandResult toCindResult() {
948             AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
949             mSignal = asuToSignal(mCM.getDefaultPhone().getSignalStrength());
950 
951             String status = "+CIND: " + mService + "," + mCall + "," + mCallsetup + "," +
952                             mCallheld + "," + mSignal + "," + mRoam + "," + mBattchg;
953             result.addResponse(status);
954             return result;
955         }
956 
toCsqResult()957         private synchronized AtCommandResult toCsqResult() {
958             AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
959             String status = "+CSQ: " + mRssi + ",99";
960             result.addResponse(status);
961             return result;
962         }
963 
964 
getCindTestResult()965         private synchronized AtCommandResult getCindTestResult() {
966             return new AtCommandResult("+CIND: (\"service\",(0-1))," + "(\"call\",(0-1))," +
967                         "(\"callsetup\",(0-3)),(\"callheld\",(0-2)),(\"signal\",(0-5))," +
968                         "(\"roam\",(0-1)),(\"battchg\",(0-5))");
969         }
970 
ignoreRing()971         private synchronized void ignoreRing() {
972             mCallsetup = 0;
973             mIgnoreRing = true;
974             if (sendUpdate()) {
975                 sendURC("+CIEV: 3," + mCallsetup);
976             }
977         }
978 
979     };
980 
981     private static final int SCO_ACCEPTED = 1;
982     private static final int SCO_CONNECTED = 2;
983     private static final int SCO_CLOSED = 3;
984     private static final int CHECK_CALL_STARTED = 4;
985     private static final int CHECK_VOICE_RECOGNITION_STARTED = 5;
986     private static final int MESSAGE_CHECK_PENDING_SCO = 6;
987 
988     private final Handler mHandler = new Handler() {
989         @Override
990         public void handleMessage(Message msg) {
991             synchronized (BluetoothHandsfree.this) {
992                 switch (msg.what) {
993                 case SCO_ACCEPTED:
994                     if (msg.arg1 == ScoSocket.STATE_CONNECTED) {
995                         if (isHeadsetConnected() && (mAudioPossible || allowAudioAnytime()) &&
996                                 mConnectedSco == null) {
997                             Log.i(TAG, "Routing audio for incoming SCO connection");
998                             mConnectedSco = (ScoSocket)msg.obj;
999                             mAudioManager.setBluetoothScoOn(true);
1000                             broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_CONNECTED,
1001                                     mHeadset.getRemoteDevice());
1002                         } else {
1003                             Log.i(TAG, "Rejecting incoming SCO connection");
1004                             ((ScoSocket)msg.obj).close();
1005                         }
1006                     } // else error trying to accept, try again
1007                     mIncomingSco = createScoSocket();
1008                     mIncomingSco.accept();
1009                     break;
1010                 case SCO_CONNECTED:
1011                     if (msg.arg1 == ScoSocket.STATE_CONNECTED && isHeadsetConnected() &&
1012                             mConnectedSco == null) {
1013                         if (VDBG) log("Routing audio for outgoing SCO conection");
1014                         mConnectedSco = (ScoSocket)msg.obj;
1015                         mAudioManager.setBluetoothScoOn(true);
1016                         broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_CONNECTED,
1017                                 mHeadset.getRemoteDevice());
1018                     } else if (msg.arg1 == ScoSocket.STATE_CONNECTED) {
1019                         if (VDBG) log("Rejecting new connected outgoing SCO socket");
1020                         ((ScoSocket)msg.obj).close();
1021                         mOutgoingSco.close();
1022                     }
1023                     mOutgoingSco = null;
1024                     break;
1025                 case SCO_CLOSED:
1026                     if (mConnectedSco == (ScoSocket)msg.obj) {
1027                         mConnectedSco.close();
1028                         mConnectedSco = null;
1029                         mAudioManager.setBluetoothScoOn(false);
1030                         broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_DISCONNECTED,
1031                                mHeadset.getRemoteDevice());
1032                     } else if (mOutgoingSco == (ScoSocket)msg.obj) {
1033                         mOutgoingSco.close();
1034                         mOutgoingSco = null;
1035                     }
1036                     break;
1037                 case CHECK_CALL_STARTED:
1038                     if (mWaitingForCallStart) {
1039                         mWaitingForCallStart = false;
1040                         Log.e(TAG, "Timeout waiting for call to start");
1041                         sendURC("ERROR");
1042                         if (mStartCallWakeLock.isHeld()) {
1043                             mStartCallWakeLock.release();
1044                         }
1045                     }
1046                     break;
1047                 case CHECK_VOICE_RECOGNITION_STARTED:
1048                     if (mWaitingForVoiceRecognition) {
1049                         mWaitingForVoiceRecognition = false;
1050                         Log.e(TAG, "Timeout waiting for voice recognition to start");
1051                         sendURC("ERROR");
1052                     }
1053                     break;
1054                 case MESSAGE_CHECK_PENDING_SCO:
1055                     if (mPendingSco && isA2dpMultiProfile()) {
1056                         Log.w(TAG, "Timeout suspending A2DP for SCO (mA2dpState = " +
1057                                 mA2dpState + "). Starting SCO anyway");
1058                         mOutgoingSco = createScoSocket();
1059                         if (!(isHeadsetConnected() &&
1060                                 mOutgoingSco.connect(mHeadset.getRemoteDevice().getAddress(),
1061                                  mHeadset.getRemoteDevice().getName()))) {
1062                             mOutgoingSco = null;
1063                         }
1064                         mPendingSco = false;
1065                     }
1066                     break;
1067                 }
1068             }
1069         }
1070     };
1071 
createScoSocket()1072     private ScoSocket createScoSocket() {
1073         return new ScoSocket(mPowerManager, mHandler, SCO_ACCEPTED, SCO_CONNECTED, SCO_CLOSED);
1074     }
1075 
broadcastAudioStateIntent(int state, BluetoothDevice device)1076     private void broadcastAudioStateIntent(int state, BluetoothDevice device) {
1077         if (VDBG) log("broadcastAudioStateIntent(" + state + ")");
1078         Intent intent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
1079         intent.putExtra(BluetoothHeadset.EXTRA_AUDIO_STATE, state);
1080         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
1081         mContext.sendBroadcast(intent, android.Manifest.permission.BLUETOOTH);
1082     }
1083 
updateBtHandsfreeAfterRadioTechnologyChange()1084     void updateBtHandsfreeAfterRadioTechnologyChange() {
1085         if(VDBG) Log.d(TAG, "updateBtHandsfreeAfterRadioTechnologyChange...");
1086 
1087         mBluetoothPhoneState.updateBtPhoneStateAfterRadioTechnologyChange();
1088     }
1089 
1090     /** Request to establish SCO (audio) connection to bluetooth
1091      * headset/handsfree, if one is connected. Does not block.
1092      * Returns false if the user has requested audio off, or if there
1093      * is some other immediate problem that will prevent BT audio.
1094      */
audioOn()1095     /* package */ synchronized boolean audioOn() {
1096         if (VDBG) log("audioOn()");
1097         if (!isHeadsetConnected()) {
1098             if (DBG) log("audioOn(): headset is not connected!");
1099             return false;
1100         }
1101         if (mHeadsetType == TYPE_HANDSFREE && !mServiceConnectionEstablished) {
1102             if (DBG) log("audioOn(): service connection not yet established!");
1103             return false;
1104         }
1105 
1106         if (mConnectedSco != null) {
1107             if (DBG) log("audioOn(): audio is already connected");
1108             return true;
1109         }
1110 
1111         if (!mUserWantsAudio) {
1112             if (DBG) log("audioOn(): user requested no audio, ignoring");
1113             return false;
1114         }
1115 
1116         if (mOutgoingSco != null) {
1117             if (DBG) log("audioOn(): outgoing SCO already in progress");
1118             return true;
1119         }
1120 
1121         if (mPendingSco) {
1122             if (DBG) log("audioOn(): SCO already pending");
1123             return true;
1124         }
1125 
1126         mA2dpSuspended = false;
1127         mPendingSco = false;
1128         if (isA2dpMultiProfile() && mA2dpState == BluetoothA2dp.STATE_PLAYING) {
1129             if (DBG) log("suspending A2DP stream for SCO");
1130             mA2dpSuspended = mA2dp.suspendSink(mA2dpDevice);
1131             if (mA2dpSuspended) {
1132                 mPendingSco = true;
1133                 Message msg = mHandler.obtainMessage(MESSAGE_CHECK_PENDING_SCO);
1134                 mHandler.sendMessageDelayed(msg, 2000);
1135             } else {
1136                 Log.w(TAG, "Could not suspend A2DP stream for SCO, going ahead with SCO");
1137             }
1138         }
1139 
1140         if (!mPendingSco) {
1141             mOutgoingSco = createScoSocket();
1142             if (!mOutgoingSco.connect(mHeadset.getRemoteDevice().getAddress(),
1143                     mHeadset.getRemoteDevice().getName())) {
1144                 mOutgoingSco = null;
1145             }
1146         }
1147 
1148         return true;
1149     }
1150 
1151     /** Used to indicate the user requested BT audio on.
1152      *  This will establish SCO (BT audio), even if the user requested it off
1153      *  previously on this call.
1154      */
userWantsAudioOn()1155     /* package */ synchronized void userWantsAudioOn() {
1156         mUserWantsAudio = true;
1157         audioOn();
1158     }
1159     /** Used to indicate the user requested BT audio off.
1160      *  This will prevent us from establishing BT audio again during this call
1161      *  if audioOn() is called.
1162      */
userWantsAudioOff()1163     /* package */ synchronized void userWantsAudioOff() {
1164         mUserWantsAudio = false;
1165         audioOff();
1166     }
1167 
1168     /** Request to disconnect SCO (audio) connection to bluetooth
1169      * headset/handsfree, if one is connected. Does not block.
1170      */
audioOff()1171     /* package */ synchronized void audioOff() {
1172         if (VDBG) log("audioOff(): mPendingSco: " + mPendingSco +
1173                 ", mConnectedSco: " + mConnectedSco +
1174                 ", mOutgoingSco: " + mOutgoingSco  +
1175                 ", mA2dpState: " + mA2dpState +
1176                 ", mA2dpSuspended: " + mA2dpSuspended +
1177                 ", mIncomingSco:" + mIncomingSco);
1178 
1179         if (mA2dpSuspended) {
1180             if (isA2dpMultiProfile()) {
1181               if (DBG) log("resuming A2DP stream after disconnecting SCO");
1182               mA2dp.resumeSink(mA2dpDevice);
1183             }
1184             mA2dpSuspended = false;
1185         }
1186 
1187         mPendingSco = false;
1188 
1189         if (mConnectedSco != null) {
1190             BluetoothDevice device = null;
1191             if (mHeadset != null) {
1192                 device = mHeadset.getRemoteDevice();
1193             }
1194             mConnectedSco.close();
1195             mConnectedSco = null;
1196             mAudioManager.setBluetoothScoOn(false);
1197             broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_DISCONNECTED, device);
1198         }
1199         if (mOutgoingSco != null) {
1200             mOutgoingSco.close();
1201             mOutgoingSco = null;
1202         }
1203 
1204     }
1205 
isAudioOn()1206     /* package */ boolean isAudioOn() {
1207         return (mConnectedSco != null);
1208     }
1209 
isA2dpMultiProfile()1210     private boolean isA2dpMultiProfile() {
1211         return mA2dp != null && mHeadset != null && mA2dpDevice != null &&
1212                 mA2dpDevice.equals(mHeadset.getRemoteDevice());
1213     }
1214 
ignoreRing()1215     /* package */ void ignoreRing() {
1216         mBluetoothPhoneState.ignoreRing();
1217     }
1218 
sendURC(String urc)1219     private void sendURC(String urc) {
1220         if (isHeadsetConnected()) {
1221             mHeadset.sendURC(urc);
1222         }
1223     }
1224 
1225     /** helper to redial last dialled number */
redial()1226     private AtCommandResult redial() {
1227         String number = mPhonebook.getLastDialledNumber();
1228         if (number == null) {
1229             // spec seems to suggest sending ERROR if we dont have a
1230             // number to redial
1231             if (VDBG) log("Bluetooth redial requested (+BLDN), but no previous " +
1232                   "outgoing calls found. Ignoring");
1233             return new AtCommandResult(AtCommandResult.ERROR);
1234         }
1235         Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1236                 Uri.fromParts("tel", number, null));
1237         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1238         mContext.startActivity(intent);
1239 
1240         // We do not immediately respond OK, wait until we get a phone state
1241         // update. If we return OK now and the handsfree immeidately requests
1242         // our phone state it will say we are not in call yet which confuses
1243         // some devices
1244         expectCallStart();
1245         return new AtCommandResult(AtCommandResult.UNSOLICITED);  // send nothing
1246     }
1247 
1248     /** Build the +CLCC result
1249      *  The complexity arises from the fact that we need to maintain the same
1250      *  CLCC index even as a call moves between states. */
gsmGetClccResult()1251     private synchronized AtCommandResult gsmGetClccResult() {
1252         // Collect all known connections
1253         Connection[] clccConnections = new Connection[GSM_MAX_CONNECTIONS];  // indexed by CLCC index
1254         LinkedList<Connection> newConnections = new LinkedList<Connection>();
1255         LinkedList<Connection> connections = new LinkedList<Connection>();
1256 
1257         Call foregroundCall = mCM.getActiveFgCall();
1258         Call backgroundCall = mCM.getFirstActiveBgCall();
1259         Call ringingCall = mCM.getFirstActiveRingingCall();
1260 
1261         if (ringingCall.getState().isAlive()) {
1262             connections.addAll(ringingCall.getConnections());
1263         }
1264         if (foregroundCall.getState().isAlive()) {
1265             connections.addAll(foregroundCall.getConnections());
1266         }
1267         if (backgroundCall.getState().isAlive()) {
1268             connections.addAll(backgroundCall.getConnections());
1269         }
1270 
1271         // Mark connections that we already known about
1272         boolean clccUsed[] = new boolean[GSM_MAX_CONNECTIONS];
1273         for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
1274             clccUsed[i] = mClccUsed[i];
1275             mClccUsed[i] = false;
1276         }
1277         for (Connection c : connections) {
1278             boolean found = false;
1279             long timestamp = c.getCreateTime();
1280             for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
1281                 if (clccUsed[i] && timestamp == mClccTimestamps[i]) {
1282                     mClccUsed[i] = true;
1283                     found = true;
1284                     clccConnections[i] = c;
1285                     break;
1286                 }
1287             }
1288             if (!found) {
1289                 newConnections.add(c);
1290             }
1291         }
1292 
1293         // Find a CLCC index for new connections
1294         while (!newConnections.isEmpty()) {
1295             // Find lowest empty index
1296             int i = 0;
1297             while (mClccUsed[i]) i++;
1298             // Find earliest connection
1299             long earliestTimestamp = newConnections.get(0).getCreateTime();
1300             Connection earliestConnection = newConnections.get(0);
1301             for (int j = 0; j < newConnections.size(); j++) {
1302                 long timestamp = newConnections.get(j).getCreateTime();
1303                 if (timestamp < earliestTimestamp) {
1304                     earliestTimestamp = timestamp;
1305                     earliestConnection = newConnections.get(j);
1306                 }
1307             }
1308 
1309             // update
1310             mClccUsed[i] = true;
1311             mClccTimestamps[i] = earliestTimestamp;
1312             clccConnections[i] = earliestConnection;
1313             newConnections.remove(earliestConnection);
1314         }
1315 
1316         // Build CLCC
1317         AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
1318         for (int i = 0; i < clccConnections.length; i++) {
1319             if (mClccUsed[i]) {
1320                 String clccEntry = connectionToClccEntry(i, clccConnections[i]);
1321                 if (clccEntry != null) {
1322                     result.addResponse(clccEntry);
1323                 }
1324             }
1325         }
1326 
1327         return result;
1328     }
1329 
1330     /** Convert a Connection object into a single +CLCC result */
connectionToClccEntry(int index, Connection c)1331     private String connectionToClccEntry(int index, Connection c) {
1332         int state;
1333         switch (c.getState()) {
1334         case ACTIVE:
1335             state = 0;
1336             break;
1337         case HOLDING:
1338             state = 1;
1339             break;
1340         case DIALING:
1341             state = 2;
1342             break;
1343         case ALERTING:
1344             state = 3;
1345             break;
1346         case INCOMING:
1347             state = 4;
1348             break;
1349         case WAITING:
1350             state = 5;
1351             break;
1352         default:
1353             return null;  // bad state
1354         }
1355 
1356         int mpty = 0;
1357         Call call = c.getCall();
1358         if (call != null) {
1359             mpty = call.isMultiparty() ? 1 : 0;
1360         }
1361 
1362         int direction = c.isIncoming() ? 1 : 0;
1363 
1364         String number = c.getAddress();
1365         int type = -1;
1366         if (number != null) {
1367             type = PhoneNumberUtils.toaFromString(number);
1368         }
1369 
1370         String result = "+CLCC: " + (index + 1) + "," + direction + "," + state + ",0," + mpty;
1371         if (number != null) {
1372             result += ",\"" + number + "\"," + type;
1373         }
1374         return result;
1375     }
1376 
1377     /** Build the +CLCC result for CDMA
1378      *  The complexity arises from the fact that we need to maintain the same
1379      *  CLCC index even as a call moves between states. */
cdmaGetClccResult()1380     private synchronized AtCommandResult cdmaGetClccResult() {
1381         // In CDMA at one time a user can have only two live/active connections
1382         Connection[] clccConnections = new Connection[CDMA_MAX_CONNECTIONS];// indexed by CLCC index
1383         Call foregroundCall = mCM.getActiveFgCall();
1384         Call ringingCall = mCM.getFirstActiveRingingCall();
1385 
1386         Call.State ringingCallState = ringingCall.getState();
1387         // If the Ringing Call state is INCOMING, that means this is the very first call
1388         // hence there should not be any Foreground Call
1389         if (ringingCallState == Call.State.INCOMING) {
1390             if (VDBG) log("Filling clccConnections[0] for INCOMING state");
1391             clccConnections[0] = ringingCall.getLatestConnection();
1392         } else if (foregroundCall.getState().isAlive()) {
1393             // Getting Foreground Call connection based on Call state
1394             if (ringingCall.isRinging()) {
1395                 if (VDBG) log("Filling clccConnections[0] & [1] for CALL WAITING state");
1396                 clccConnections[0] = foregroundCall.getEarliestConnection();
1397                 clccConnections[1] = ringingCall.getLatestConnection();
1398             } else {
1399                 if (foregroundCall.getConnections().size() <= 1) {
1400                     // Single call scenario
1401                     if (VDBG) log("Filling clccConnections[0] with ForgroundCall latest connection");
1402                     clccConnections[0] = foregroundCall.getLatestConnection();
1403                 } else {
1404                     // Multiple Call scenario. This would be true for both
1405                     // CONF_CALL and THRWAY_ACTIVE state
1406                     if (VDBG) log("Filling clccConnections[0] & [1] with ForgroundCall connections");
1407                     clccConnections[0] = foregroundCall.getEarliestConnection();
1408                     clccConnections[1] = foregroundCall.getLatestConnection();
1409                 }
1410             }
1411         }
1412 
1413         // Update the mCdmaIsSecondCallActive flag based on the Phone call state
1414         if (PhoneApp.getInstance().cdmaPhoneCallState.getCurrentCallState()
1415                 == CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE) {
1416             cdmaSetSecondCallState(false);
1417         } else if (PhoneApp.getInstance().cdmaPhoneCallState.getCurrentCallState()
1418                 == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
1419             cdmaSetSecondCallState(true);
1420         }
1421 
1422         // Build CLCC
1423         AtCommandResult result = new AtCommandResult(AtCommandResult.OK);
1424         for (int i = 0; (i < clccConnections.length) && (clccConnections[i] != null); i++) {
1425             String clccEntry = cdmaConnectionToClccEntry(i, clccConnections[i]);
1426             if (clccEntry != null) {
1427                 result.addResponse(clccEntry);
1428             }
1429         }
1430 
1431         return result;
1432     }
1433 
1434     /** Convert a Connection object into a single +CLCC result for CDMA phones */
cdmaConnectionToClccEntry(int index, Connection c)1435     private String cdmaConnectionToClccEntry(int index, Connection c) {
1436         int state;
1437         PhoneApp app = PhoneApp.getInstance();
1438         CdmaPhoneCallState.PhoneCallState currCdmaCallState =
1439                 app.cdmaPhoneCallState.getCurrentCallState();
1440         CdmaPhoneCallState.PhoneCallState prevCdmaCallState =
1441                 app.cdmaPhoneCallState.getPreviousCallState();
1442 
1443         if ((prevCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
1444                 && (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL)) {
1445             // If the current state is reached after merging two calls
1446             // we set the state of all the connections as ACTIVE
1447             state = 0;
1448         } else {
1449             switch (c.getState()) {
1450             case ACTIVE:
1451                 // For CDMA since both the connections are set as active by FW after accepting
1452                 // a Call waiting or making a 3 way call, we need to set the state specifically
1453                 // to ACTIVE/HOLDING based on the mCdmaIsSecondCallActive flag. This way the
1454                 // CLCC result will allow BT devices to enable the swap or merge options
1455                 if (index == 0) { // For the 1st active connection
1456                     state = mCdmaIsSecondCallActive ? 1 : 0;
1457                 } else { // for the 2nd active connection
1458                     state = mCdmaIsSecondCallActive ? 0 : 1;
1459                 }
1460                 break;
1461             case HOLDING:
1462                 state = 1;
1463                 break;
1464             case DIALING:
1465                 state = 2;
1466                 break;
1467             case ALERTING:
1468                 state = 3;
1469                 break;
1470             case INCOMING:
1471                 state = 4;
1472                 break;
1473             case WAITING:
1474                 state = 5;
1475                 break;
1476             default:
1477                 return null;  // bad state
1478             }
1479         }
1480 
1481         int mpty = 0;
1482         if (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
1483             if (prevCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
1484                 // If the current state is reached after merging two calls
1485                 // we set the multiparty call true.
1486                 mpty = 1;
1487             } else {
1488                 // CALL_CONF state is not from merging two calls, but from
1489                 // accepting the second call. In this case first will be on
1490                 // hold.
1491                 mpty = 0;
1492             }
1493         } else {
1494             mpty = 0;
1495         }
1496 
1497         int direction = c.isIncoming() ? 1 : 0;
1498 
1499         String number = c.getAddress();
1500         int type = -1;
1501         if (number != null) {
1502             type = PhoneNumberUtils.toaFromString(number);
1503         }
1504 
1505         String result = "+CLCC: " + (index + 1) + "," + direction + "," + state + ",0," + mpty;
1506         if (number != null) {
1507             result += ",\"" + number + "\"," + type;
1508         }
1509         return result;
1510     }
1511 
1512     /**
1513      * Register AT Command handlers to implement the Headset profile
1514      */
initializeHeadsetAtParser()1515     private void initializeHeadsetAtParser() {
1516         if (VDBG) log("Registering Headset AT commands");
1517         AtParser parser = mHeadset.getAtParser();
1518         // Headset's usually only have one button, which is meant to cause the
1519         // HS to send us AT+CKPD=200 or AT+CKPD.
1520         parser.register("+CKPD", new AtCommandHandler() {
1521             private AtCommandResult headsetButtonPress() {
1522                 if (mCM.getFirstActiveRingingCall().isRinging()) {
1523                     // Answer the call
1524                     mBluetoothPhoneState.stopRing();
1525                     sendURC("OK");
1526                     PhoneUtils.answerCall(mCM.getFirstActiveRingingCall());
1527                     // If in-band ring tone is supported, SCO connection will already
1528                     // be up and the following call will just return.
1529                     audioOn();
1530                     return new AtCommandResult(AtCommandResult.UNSOLICITED);
1531                 } else if (mCM.hasActiveFgCall()) {
1532                     if (!isAudioOn()) {
1533                         // Transfer audio from AG to HS
1534                         audioOn();
1535                     } else {
1536                         if (mHeadset.getDirection() == HeadsetBase.DIRECTION_INCOMING &&
1537                           (System.currentTimeMillis() - mHeadset.getConnectTimestamp()) < 5000) {
1538                             // Headset made a recent ACL connection to us - and
1539                             // made a mandatory AT+CKPD request to connect
1540                             // audio which races with our automatic audio
1541                             // setup.  ignore
1542                         } else {
1543                             // Hang up the call
1544                             audioOff();
1545                             PhoneUtils.hangup(PhoneApp.getInstance().mCM);
1546                         }
1547                     }
1548                     return new AtCommandResult(AtCommandResult.OK);
1549                 } else {
1550                     // No current call - redial last number
1551                     return redial();
1552                 }
1553             }
1554             @Override
1555             public AtCommandResult handleActionCommand() {
1556                 return headsetButtonPress();
1557             }
1558             @Override
1559             public AtCommandResult handleSetCommand(Object[] args) {
1560                 return headsetButtonPress();
1561             }
1562         });
1563     }
1564 
1565     /**
1566      * Register AT Command handlers to implement the Handsfree profile
1567      */
initializeHandsfreeAtParser()1568     private void initializeHandsfreeAtParser() {
1569         if (VDBG) log("Registering Handsfree AT commands");
1570         AtParser parser = mHeadset.getAtParser();
1571         final Phone phone = mCM.getDefaultPhone();
1572 
1573         // Answer
1574         parser.register('A', new AtCommandHandler() {
1575             @Override
1576             public AtCommandResult handleBasicCommand(String args) {
1577                 sendURC("OK");
1578                 mBluetoothPhoneState.stopRing();
1579                 PhoneUtils.answerCall(mCM.getFirstActiveRingingCall());
1580                 return new AtCommandResult(AtCommandResult.UNSOLICITED);
1581             }
1582         });
1583         parser.register('D', new AtCommandHandler() {
1584             @Override
1585             public AtCommandResult handleBasicCommand(String args) {
1586                 if (args.length() > 0) {
1587                     if (args.charAt(0) == '>') {
1588                         // Yuck - memory dialling requested.
1589                         // Just dial last number for now
1590                         if (args.startsWith(">9999")) {   // for PTS test
1591                             return new AtCommandResult(AtCommandResult.ERROR);
1592                         }
1593                         return redial();
1594                     } else {
1595                         // Remove trailing ';'
1596                         if (args.charAt(args.length() - 1) == ';') {
1597                             args = args.substring(0, args.length() - 1);
1598                         }
1599                         Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1600                                 Uri.fromParts("tel", args, null));
1601                         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1602                         mContext.startActivity(intent);
1603 
1604                         expectCallStart();
1605                         return new AtCommandResult(AtCommandResult.UNSOLICITED);  // send nothing
1606                     }
1607                 }
1608                 return new AtCommandResult(AtCommandResult.ERROR);
1609             }
1610         });
1611 
1612         // Hang-up command
1613         parser.register("+CHUP", new AtCommandHandler() {
1614             @Override
1615             public AtCommandResult handleActionCommand() {
1616                 sendURC("OK");
1617                 if (mCM.hasActiveFgCall()) {
1618                     PhoneUtils.hangupActiveCall(mCM.getActiveFgCall());
1619                 } else if (mCM.hasActiveRingingCall()) {
1620                     PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());
1621                 } else if (mCM.hasActiveBgCall()) {
1622                     PhoneUtils.hangupHoldingCall(mCM.getFirstActiveBgCall());
1623                 }
1624                 return new AtCommandResult(AtCommandResult.UNSOLICITED);
1625             }
1626         });
1627 
1628         // Bluetooth Retrieve Supported Features command
1629         parser.register("+BRSF", new AtCommandHandler() {
1630             private AtCommandResult sendBRSF() {
1631                 return new AtCommandResult("+BRSF: " + mLocalBrsf);
1632             }
1633             @Override
1634             public AtCommandResult handleSetCommand(Object[] args) {
1635                 // AT+BRSF=<handsfree supported features bitmap>
1636                 // Handsfree is telling us which features it supports. We
1637                 // send the features we support
1638                 if (args.length == 1 && (args[0] instanceof Integer)) {
1639                     mRemoteBrsf = (Integer) args[0];
1640                 } else {
1641                     Log.w(TAG, "HF didn't sent BRSF assuming 0");
1642                 }
1643                 return sendBRSF();
1644             }
1645             @Override
1646             public AtCommandResult handleActionCommand() {
1647                 // This seems to be out of spec, but lets do the nice thing
1648                 return sendBRSF();
1649             }
1650             @Override
1651             public AtCommandResult handleReadCommand() {
1652                 // This seems to be out of spec, but lets do the nice thing
1653                 return sendBRSF();
1654             }
1655         });
1656 
1657         // Call waiting notification on/off
1658         parser.register("+CCWA", new AtCommandHandler() {
1659             @Override
1660             public AtCommandResult handleActionCommand() {
1661                 // Seems to be out of spec, but lets return nicely
1662                 return new AtCommandResult(AtCommandResult.OK);
1663             }
1664             @Override
1665             public AtCommandResult handleReadCommand() {
1666                 // Call waiting is always on
1667                 return new AtCommandResult("+CCWA: 1");
1668             }
1669             @Override
1670             public AtCommandResult handleSetCommand(Object[] args) {
1671                 // AT+CCWA=<n>
1672                 // Handsfree is trying to enable/disable call waiting. We
1673                 // cannot disable in the current implementation.
1674                 return new AtCommandResult(AtCommandResult.OK);
1675             }
1676             @Override
1677             public AtCommandResult handleTestCommand() {
1678                 // Request for range of supported CCWA paramters
1679                 return new AtCommandResult("+CCWA: (\"n\",(1))");
1680             }
1681         });
1682 
1683         // Mobile Equipment Event Reporting enable/disable command
1684         // Of the full 3GPP syntax paramters (mode, keyp, disp, ind, bfr) we
1685         // only support paramter ind (disable/enable evert reporting using
1686         // +CDEV)
1687         parser.register("+CMER", new AtCommandHandler() {
1688             @Override
1689             public AtCommandResult handleReadCommand() {
1690                 return new AtCommandResult(
1691                         "+CMER: 3,0,0," + (mIndicatorsEnabled ? "1" : "0"));
1692             }
1693             @Override
1694             public AtCommandResult handleSetCommand(Object[] args) {
1695                 if (args.length < 4) {
1696                     // This is a syntax error
1697                     return new AtCommandResult(AtCommandResult.ERROR);
1698                 } else if (args[0].equals(3) && args[1].equals(0) &&
1699                            args[2].equals(0)) {
1700                     boolean valid = false;
1701                     if (args[3].equals(0)) {
1702                         mIndicatorsEnabled = false;
1703                         valid = true;
1704                     } else if (args[3].equals(1)) {
1705                         mIndicatorsEnabled = true;
1706                         valid = true;
1707                     }
1708                     if (valid) {
1709                         if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) == 0x0) {
1710                             mServiceConnectionEstablished = true;
1711                             sendURC("OK");  // send immediately, then initiate audio
1712                             if (isIncallAudio()) {
1713                                 audioOn();
1714                             }
1715                             // only send OK once
1716                             return new AtCommandResult(AtCommandResult.UNSOLICITED);
1717                         } else {
1718                             return new AtCommandResult(AtCommandResult.OK);
1719                         }
1720                     }
1721                 }
1722                 return reportCmeError(BluetoothCmeError.OPERATION_NOT_SUPPORTED);
1723             }
1724             @Override
1725             public AtCommandResult handleTestCommand() {
1726                 return new AtCommandResult("+CMER: (3),(0),(0),(0-1)");
1727             }
1728         });
1729 
1730         // Mobile Equipment Error Reporting enable/disable
1731         parser.register("+CMEE", new AtCommandHandler() {
1732             @Override
1733             public AtCommandResult handleActionCommand() {
1734                 // out of spec, assume they want to enable
1735                 mCmee = true;
1736                 return new AtCommandResult(AtCommandResult.OK);
1737             }
1738             @Override
1739             public AtCommandResult handleReadCommand() {
1740                 return new AtCommandResult("+CMEE: " + (mCmee ? "1" : "0"));
1741             }
1742             @Override
1743             public AtCommandResult handleSetCommand(Object[] args) {
1744                 // AT+CMEE=<n>
1745                 if (args.length == 0) {
1746                     // <n> ommitted - default to 0
1747                     mCmee = false;
1748                     return new AtCommandResult(AtCommandResult.OK);
1749                 } else if (!(args[0] instanceof Integer)) {
1750                     // Syntax error
1751                     return new AtCommandResult(AtCommandResult.ERROR);
1752                 } else {
1753                     mCmee = ((Integer)args[0] == 1);
1754                     return new AtCommandResult(AtCommandResult.OK);
1755                 }
1756             }
1757             @Override
1758             public AtCommandResult handleTestCommand() {
1759                 // Probably not required but spec, but no harm done
1760                 return new AtCommandResult("+CMEE: (0-1)");
1761             }
1762         });
1763 
1764         // Bluetooth Last Dialled Number
1765         parser.register("+BLDN", new AtCommandHandler() {
1766             @Override
1767             public AtCommandResult handleActionCommand() {
1768                 return redial();
1769             }
1770         });
1771 
1772         // Indicator Update command
1773         parser.register("+CIND", new AtCommandHandler() {
1774             @Override
1775             public AtCommandResult handleReadCommand() {
1776                 return mBluetoothPhoneState.toCindResult();
1777             }
1778             @Override
1779             public AtCommandResult handleTestCommand() {
1780                 return mBluetoothPhoneState.getCindTestResult();
1781             }
1782         });
1783 
1784         // Query Signal Quality (legacy)
1785         parser.register("+CSQ", new AtCommandHandler() {
1786             @Override
1787             public AtCommandResult handleActionCommand() {
1788                 return mBluetoothPhoneState.toCsqResult();
1789             }
1790         });
1791 
1792         // Query network registration state
1793         parser.register("+CREG", new AtCommandHandler() {
1794             @Override
1795             public AtCommandResult handleReadCommand() {
1796                 return new AtCommandResult(mBluetoothPhoneState.toCregString());
1797             }
1798         });
1799 
1800         // Send DTMF. I don't know if we are also expected to play the DTMF tone
1801         // locally, right now we don't
1802         parser.register("+VTS", new AtCommandHandler() {
1803             @Override
1804             public AtCommandResult handleSetCommand(Object[] args) {
1805                 if (args.length >= 1) {
1806                     char c;
1807                     if (args[0] instanceof Integer) {
1808                         c = ((Integer) args[0]).toString().charAt(0);
1809                     } else {
1810                         c = ((String) args[0]).charAt(0);
1811                     }
1812                     if (isValidDtmf(c)) {
1813                         phone.sendDtmf(c);
1814                         return new AtCommandResult(AtCommandResult.OK);
1815                     }
1816                 }
1817                 return new AtCommandResult(AtCommandResult.ERROR);
1818             }
1819             private boolean isValidDtmf(char c) {
1820                 switch (c) {
1821                 case '#':
1822                 case '*':
1823                     return true;
1824                 default:
1825                     if (Character.digit(c, 14) != -1) {
1826                         return true;  // 0-9 and A-D
1827                     }
1828                     return false;
1829                 }
1830             }
1831         });
1832 
1833         // List calls
1834         parser.register("+CLCC", new AtCommandHandler() {
1835             @Override
1836             public AtCommandResult handleActionCommand() {
1837                 int phoneType = phone.getPhoneType();
1838                 if (phoneType == Phone.PHONE_TYPE_CDMA) {
1839                     return cdmaGetClccResult();
1840                 } else if (phoneType == Phone.PHONE_TYPE_GSM) {
1841                     return gsmGetClccResult();
1842                 } else {
1843                     throw new IllegalStateException("Unexpected phone type: " + phoneType);
1844                 }
1845             }
1846         });
1847 
1848         // Call Hold and Multiparty Handling command
1849         parser.register("+CHLD", new AtCommandHandler() {
1850             @Override
1851             public AtCommandResult handleSetCommand(Object[] args) {
1852                 int phoneType = phone.getPhoneType();
1853                 Call ringingCall = mCM.getFirstActiveRingingCall();
1854                 Call backgroundCall = mCM.getFirstActiveBgCall();
1855 
1856                 if (args.length >= 1) {
1857                     if (args[0].equals(0)) {
1858                         boolean result;
1859                         if (ringingCall.isRinging()) {
1860                             result = PhoneUtils.hangupRingingCall(ringingCall);
1861                         } else {
1862                             result = PhoneUtils.hangupHoldingCall(backgroundCall);
1863                         }
1864                         if (result) {
1865                             return new AtCommandResult(AtCommandResult.OK);
1866                         } else {
1867                             return new AtCommandResult(AtCommandResult.ERROR);
1868                         }
1869                     } else if (args[0].equals(1)) {
1870                         if (phoneType == Phone.PHONE_TYPE_CDMA) {
1871                             if (ringingCall.isRinging()) {
1872                                 // Hangup the active call and then answer call waiting call.
1873                                 if (VDBG) log("CHLD:1 Callwaiting Answer call");
1874                                 PhoneUtils.hangup(PhoneApp.getInstance().mCM);
1875                                 PhoneUtils.answerCall(ringingCall);
1876                                 PhoneUtils.setMute(false);
1877                             } else {
1878                                 // If there is no Call waiting then just hangup
1879                                 // the active call. In CDMA this mean that the complete
1880                                 // call session would be ended
1881                                 if (VDBG) log("CHLD:1 Hangup Call");
1882                                 PhoneUtils.hangup(PhoneApp.getInstance().mCM);
1883                             }
1884                             return new AtCommandResult(AtCommandResult.OK);
1885                         } else if (phoneType == Phone.PHONE_TYPE_GSM) {
1886                             // Hangup active call, answer held call
1887                             if (PhoneUtils.answerAndEndActive(
1888                                     PhoneApp.getInstance().mCM, ringingCall)) {
1889                                 return new AtCommandResult(AtCommandResult.OK);
1890                             } else {
1891                                 return new AtCommandResult(AtCommandResult.ERROR);
1892                             }
1893                         } else {
1894                             throw new IllegalStateException("Unexpected phone type: " + phoneType);
1895                         }
1896                     } else if (args[0].equals(2)) {
1897                         sendURC("OK");
1898                         if (phoneType == Phone.PHONE_TYPE_CDMA) {
1899                             // For CDMA, the way we switch to a new incoming call is by
1900                             // calling PhoneUtils.answerCall(). switchAndHoldActive() won't
1901                             // properly update the call state within telephony.
1902                             // If the Phone state is already in CONF_CALL then we simply send
1903                             // a flash cmd by calling switchHoldingAndActive()
1904                             if (ringingCall.isRinging()) {
1905                                 if (VDBG) log("CHLD:2 Callwaiting Answer call");
1906                                 PhoneUtils.answerCall(ringingCall);
1907                                 PhoneUtils.setMute(false);
1908                                 // Setting the second callers state flag to TRUE (i.e. active)
1909                                 cdmaSetSecondCallState(true);
1910                             } else if (PhoneApp.getInstance().cdmaPhoneCallState
1911                                     .getCurrentCallState()
1912                                     == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
1913                                 if (VDBG) log("CHLD:2 Swap Calls");
1914                                 PhoneUtils.switchHoldingAndActive(backgroundCall);
1915                                 // Toggle the second callers active state flag
1916                                 cdmaSwapSecondCallState();
1917                             }
1918                         } else if (phoneType == Phone.PHONE_TYPE_GSM) {
1919                             PhoneUtils.switchHoldingAndActive(backgroundCall);
1920                         } else {
1921                             throw new IllegalStateException("Unexpected phone type: " + phoneType);
1922                         }
1923                         return new AtCommandResult(AtCommandResult.UNSOLICITED);
1924                     } else if (args[0].equals(3)) {
1925                         sendURC("OK");
1926                         if (phoneType == Phone.PHONE_TYPE_CDMA) {
1927                             CdmaPhoneCallState.PhoneCallState state =
1928                                 PhoneApp.getInstance().cdmaPhoneCallState.getCurrentCallState();
1929                             // For CDMA, we need to check if the call is in THRWAY_ACTIVE state
1930                             if (state == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
1931                                 if (VDBG) log("CHLD:3 Merge Calls");
1932                                 PhoneUtils.mergeCalls();
1933                             } else if (state == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
1934                                 // State is CONF_CALL already and we are getting a merge call
1935                                 // This can happen when CONF_CALL was entered from a Call Waiting
1936                                 mBluetoothPhoneState.updateCallHeld();
1937                             }
1938                         } else if (phoneType == Phone.PHONE_TYPE_GSM) {
1939                             if (mCM.hasActiveFgCall() && mCM.hasActiveBgCall()) {
1940                                 PhoneUtils.mergeCalls();
1941                             }
1942                         } else {
1943                             throw new IllegalStateException("Unexpected phone type: " + phoneType);
1944                         }
1945                         return new AtCommandResult(AtCommandResult.UNSOLICITED);
1946                     }
1947                 }
1948                 return new AtCommandResult(AtCommandResult.ERROR);
1949             }
1950             @Override
1951             public AtCommandResult handleTestCommand() {
1952                 mServiceConnectionEstablished = true;
1953                 sendURC("+CHLD: (0,1,2,3)");
1954                 sendURC("OK");  // send reply first, then connect audio
1955                 if (isIncallAudio()) {
1956                     audioOn();
1957                 }
1958                 // already replied
1959                 return new AtCommandResult(AtCommandResult.UNSOLICITED);
1960             }
1961         });
1962 
1963         // Get Network operator name
1964         parser.register("+COPS", new AtCommandHandler() {
1965             @Override
1966             public AtCommandResult handleReadCommand() {
1967                 String operatorName = phone.getServiceState().getOperatorAlphaLong();
1968                 if (operatorName != null) {
1969                     if (operatorName.length() > 16) {
1970                         operatorName = operatorName.substring(0, 16);
1971                     }
1972                     return new AtCommandResult(
1973                             "+COPS: 0,0,\"" + operatorName + "\"");
1974                 } else {
1975                     return new AtCommandResult(
1976                             "+COPS: 0,0,\"UNKNOWN\",0");
1977                 }
1978             }
1979             @Override
1980             public AtCommandResult handleSetCommand(Object[] args) {
1981                 // Handsfree only supports AT+COPS=3,0
1982                 if (args.length != 2 || !(args[0] instanceof Integer)
1983                     || !(args[1] instanceof Integer)) {
1984                     // syntax error
1985                     return new AtCommandResult(AtCommandResult.ERROR);
1986                 } else if ((Integer)args[0] != 3 || (Integer)args[1] != 0) {
1987                     return reportCmeError(BluetoothCmeError.OPERATION_NOT_SUPPORTED);
1988                 } else {
1989                     return new AtCommandResult(AtCommandResult.OK);
1990                 }
1991             }
1992             @Override
1993             public AtCommandResult handleTestCommand() {
1994                 // Out of spec, but lets be friendly
1995                 return new AtCommandResult("+COPS: (3),(0)");
1996             }
1997         });
1998 
1999         // Mobile PIN
2000         // AT+CPIN is not in the handsfree spec (although it is in 3GPP)
2001         parser.register("+CPIN", new AtCommandHandler() {
2002             @Override
2003             public AtCommandResult handleReadCommand() {
2004                 return new AtCommandResult("+CPIN: READY");
2005             }
2006         });
2007 
2008         // Bluetooth Response and Hold
2009         // Only supported on PDC (Japan) and CDMA networks.
2010         parser.register("+BTRH", new AtCommandHandler() {
2011             @Override
2012             public AtCommandResult handleReadCommand() {
2013                 // Replying with just OK indicates no response and hold
2014                 // features in use now
2015                 return new AtCommandResult(AtCommandResult.OK);
2016             }
2017             @Override
2018             public AtCommandResult handleSetCommand(Object[] args) {
2019                 // Neeed PDC or CDMA
2020                 return new AtCommandResult(AtCommandResult.ERROR);
2021             }
2022         });
2023 
2024         // Request International Mobile Subscriber Identity (IMSI)
2025         // Not in bluetooth handset spec
2026         parser.register("+CIMI", new AtCommandHandler() {
2027             @Override
2028             public AtCommandResult handleActionCommand() {
2029                 // AT+CIMI
2030                 String imsi = phone.getSubscriberId();
2031                 if (imsi == null || imsi.length() == 0) {
2032                     return reportCmeError(BluetoothCmeError.SIM_FAILURE);
2033                 } else {
2034                     return new AtCommandResult(imsi);
2035                 }
2036             }
2037         });
2038 
2039         // Calling Line Identification Presentation
2040         parser.register("+CLIP", new AtCommandHandler() {
2041             @Override
2042             public AtCommandResult handleReadCommand() {
2043                 // Currently assumes the network is provisioned for CLIP
2044                 return new AtCommandResult("+CLIP: " + (mClip ? "1" : "0") + ",1");
2045             }
2046             @Override
2047             public AtCommandResult handleSetCommand(Object[] args) {
2048                 // AT+CLIP=<n>
2049                 if (args.length >= 1 && (args[0].equals(0) || args[0].equals(1))) {
2050                     mClip = args[0].equals(1);
2051                     return new AtCommandResult(AtCommandResult.OK);
2052                 } else {
2053                     return new AtCommandResult(AtCommandResult.ERROR);
2054                 }
2055             }
2056             @Override
2057             public AtCommandResult handleTestCommand() {
2058                 return new AtCommandResult("+CLIP: (0-1)");
2059             }
2060         });
2061 
2062         // AT+CGSN - Returns the device IMEI number.
2063         parser.register("+CGSN", new AtCommandHandler() {
2064             @Override
2065             public AtCommandResult handleActionCommand() {
2066                 // Get the IMEI of the device.
2067                 // phone will not be NULL at this point.
2068                 return new AtCommandResult("+CGSN: " + phone.getDeviceId());
2069             }
2070         });
2071 
2072         // AT+CGMM - Query Model Information
2073         parser.register("+CGMM", new AtCommandHandler() {
2074             @Override
2075             public AtCommandResult handleActionCommand() {
2076                 // Return the Model Information.
2077                 String model = SystemProperties.get("ro.product.model");
2078                 if (model != null) {
2079                     return new AtCommandResult("+CGMM: " + model);
2080                 } else {
2081                     return new AtCommandResult(AtCommandResult.ERROR);
2082                 }
2083             }
2084         });
2085 
2086         // AT+CGMI - Query Manufacturer Information
2087         parser.register("+CGMI", new AtCommandHandler() {
2088             @Override
2089             public AtCommandResult handleActionCommand() {
2090                 // Return the Model Information.
2091                 String manuf = SystemProperties.get("ro.product.manufacturer");
2092                 if (manuf != null) {
2093                     return new AtCommandResult("+CGMI: " + manuf);
2094                 } else {
2095                     return new AtCommandResult(AtCommandResult.ERROR);
2096                 }
2097             }
2098         });
2099 
2100         // Noise Reduction and Echo Cancellation control
2101         parser.register("+NREC", new AtCommandHandler() {
2102             @Override
2103             public AtCommandResult handleSetCommand(Object[] args) {
2104                 if (args[0].equals(0)) {
2105                     mAudioManager.setParameters(HEADSET_NREC+"=off");
2106                     return new AtCommandResult(AtCommandResult.OK);
2107                 } else if (args[0].equals(1)) {
2108                     mAudioManager.setParameters(HEADSET_NREC+"=on");
2109                     return new AtCommandResult(AtCommandResult.OK);
2110                 }
2111                 return new AtCommandResult(AtCommandResult.ERROR);
2112             }
2113         });
2114 
2115         // Voice recognition (dialing)
2116         parser.register("+BVRA", new AtCommandHandler() {
2117             @Override
2118             public AtCommandResult handleSetCommand(Object[] args) {
2119                 if (!BluetoothHeadset.isBluetoothVoiceDialingEnabled(mContext)) {
2120                     return new AtCommandResult(AtCommandResult.ERROR);
2121                 }
2122                 if (args.length >= 1 && args[0].equals(1)) {
2123                     synchronized (BluetoothHandsfree.this) {
2124                         if (!mWaitingForVoiceRecognition) {
2125                             try {
2126                                 mContext.startActivity(sVoiceCommandIntent);
2127                             } catch (ActivityNotFoundException e) {
2128                                 return new AtCommandResult(AtCommandResult.ERROR);
2129                             }
2130                             expectVoiceRecognition();
2131                         }
2132                     }
2133                     return new AtCommandResult(AtCommandResult.UNSOLICITED);  // send nothing yet
2134                 } else if (args.length >= 1 && args[0].equals(0)) {
2135                     audioOff();
2136                     return new AtCommandResult(AtCommandResult.OK);
2137                 }
2138                 return new AtCommandResult(AtCommandResult.ERROR);
2139             }
2140             @Override
2141             public AtCommandResult handleTestCommand() {
2142                 return new AtCommandResult("+BVRA: (0-1)");
2143             }
2144         });
2145 
2146         // Retrieve Subscriber Number
2147         parser.register("+CNUM", new AtCommandHandler() {
2148             @Override
2149             public AtCommandResult handleActionCommand() {
2150                 String number = phone.getLine1Number();
2151                 if (number == null) {
2152                     return new AtCommandResult(AtCommandResult.OK);
2153                 }
2154                 return new AtCommandResult("+CNUM: ,\"" + number + "\"," +
2155                         PhoneNumberUtils.toaFromString(number) + ",,4");
2156             }
2157         });
2158 
2159         // Microphone Gain
2160         parser.register("+VGM", new AtCommandHandler() {
2161             @Override
2162             public AtCommandResult handleSetCommand(Object[] args) {
2163                 // AT+VGM=<gain>    in range [0,15]
2164                 // Headset/Handsfree is reporting its current gain setting
2165                 return new AtCommandResult(AtCommandResult.OK);
2166             }
2167         });
2168 
2169         // Speaker Gain
2170         parser.register("+VGS", new AtCommandHandler() {
2171             @Override
2172             public AtCommandResult handleSetCommand(Object[] args) {
2173                 // AT+VGS=<gain>    in range [0,15]
2174                 if (args.length != 1 || !(args[0] instanceof Integer)) {
2175                     return new AtCommandResult(AtCommandResult.ERROR);
2176                 }
2177                 mScoGain = (Integer) args[0];
2178                 int flag =  mAudioManager.isBluetoothScoOn() ? AudioManager.FLAG_SHOW_UI:0;
2179 
2180                 mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, mScoGain, flag);
2181                 return new AtCommandResult(AtCommandResult.OK);
2182             }
2183         });
2184 
2185         // Phone activity status
2186         parser.register("+CPAS", new AtCommandHandler() {
2187             @Override
2188             public AtCommandResult handleActionCommand() {
2189                 int status = 0;
2190                 switch (mCM.getState()) {
2191                 case IDLE:
2192                     status = 0;
2193                     break;
2194                 case RINGING:
2195                     status = 3;
2196                     break;
2197                 case OFFHOOK:
2198                     status = 4;
2199                     break;
2200                 }
2201                 return new AtCommandResult("+CPAS: " + status);
2202             }
2203         });
2204         mPhonebook.register(parser);
2205     }
2206 
sendScoGainUpdate(int gain)2207     public void sendScoGainUpdate(int gain) {
2208         if (mScoGain != gain && (mRemoteBrsf & BRSF_HF_REMOTE_VOL_CONTROL) != 0x0) {
2209             sendURC("+VGS:" + gain);
2210             mScoGain = gain;
2211         }
2212     }
2213 
reportCmeError(int error)2214     public AtCommandResult reportCmeError(int error) {
2215         if (mCmee) {
2216             AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED);
2217             result.addResponse("+CME ERROR: " + error);
2218             return result;
2219         } else {
2220             return new AtCommandResult(AtCommandResult.ERROR);
2221         }
2222     }
2223 
2224     private static final int START_CALL_TIMEOUT = 10000;  // ms
2225 
expectCallStart()2226     private synchronized void expectCallStart() {
2227         mWaitingForCallStart = true;
2228         Message msg = Message.obtain(mHandler, CHECK_CALL_STARTED);
2229         mHandler.sendMessageDelayed(msg, START_CALL_TIMEOUT);
2230         if (!mStartCallWakeLock.isHeld()) {
2231             mStartCallWakeLock.acquire(START_CALL_TIMEOUT);
2232         }
2233     }
2234 
callStarted()2235     private synchronized void callStarted() {
2236         if (mWaitingForCallStart) {
2237             mWaitingForCallStart = false;
2238             sendURC("OK");
2239             if (mStartCallWakeLock.isHeld()) {
2240                 mStartCallWakeLock.release();
2241             }
2242         }
2243     }
2244 
2245     private static final int START_VOICE_RECOGNITION_TIMEOUT = 5000;  // ms
2246 
expectVoiceRecognition()2247     private synchronized void expectVoiceRecognition() {
2248         mWaitingForVoiceRecognition = true;
2249         Message msg = Message.obtain(mHandler, CHECK_VOICE_RECOGNITION_STARTED);
2250         mHandler.sendMessageDelayed(msg, START_VOICE_RECOGNITION_TIMEOUT);
2251         if (!mStartVoiceRecognitionWakeLock.isHeld()) {
2252             mStartVoiceRecognitionWakeLock.acquire(START_VOICE_RECOGNITION_TIMEOUT);
2253         }
2254     }
2255 
startVoiceRecognition()2256     /* package */ synchronized boolean startVoiceRecognition() {
2257         if (mWaitingForVoiceRecognition) {
2258             // HF initiated
2259             mWaitingForVoiceRecognition = false;
2260             sendURC("OK");
2261         } else {
2262             // AG initiated
2263             sendURC("+BVRA: 1");
2264         }
2265         boolean ret = audioOn();
2266         if (mStartVoiceRecognitionWakeLock.isHeld()) {
2267             mStartVoiceRecognitionWakeLock.release();
2268         }
2269         return ret;
2270     }
2271 
stopVoiceRecognition()2272     /* package */ synchronized boolean stopVoiceRecognition() {
2273         sendURC("+BVRA: 0");
2274         audioOff();
2275         return true;
2276     }
2277 
inDebug()2278     private boolean inDebug() {
2279         return DBG && SystemProperties.getBoolean(DebugThread.DEBUG_HANDSFREE, false);
2280     }
2281 
allowAudioAnytime()2282     private boolean allowAudioAnytime() {
2283         return inDebug() && SystemProperties.getBoolean(DebugThread.DEBUG_HANDSFREE_AUDIO_ANYTIME,
2284                 false);
2285     }
2286 
startDebug()2287     private void startDebug() {
2288         if (DBG && mDebugThread == null) {
2289             mDebugThread = new DebugThread();
2290             mDebugThread.start();
2291         }
2292     }
2293 
stopDebug()2294     private void stopDebug() {
2295         if (mDebugThread != null) {
2296             mDebugThread.interrupt();
2297             mDebugThread = null;
2298         }
2299     }
2300 
2301     /** Debug thread to read debug properties - runs when debug.bt.hfp is true
2302      *  at the time a bluetooth handsfree device is connected. Debug properties
2303      *  are polled and mock updates sent every 1 second */
2304     private class DebugThread extends Thread {
2305         /** Turns on/off handsfree profile debugging mode */
2306         private static final String DEBUG_HANDSFREE = "debug.bt.hfp";
2307 
2308         /** Mock battery level change - use 0 to 5 */
2309         private static final String DEBUG_HANDSFREE_BATTERY = "debug.bt.hfp.battery";
2310 
2311         /** Mock no cellular service when false */
2312         private static final String DEBUG_HANDSFREE_SERVICE = "debug.bt.hfp.service";
2313 
2314         /** Mock cellular roaming when true */
2315         private static final String DEBUG_HANDSFREE_ROAM = "debug.bt.hfp.roam";
2316 
2317         /** false to true transition will force an audio (SCO) connection to
2318          *  be established. true to false will force audio to be disconnected
2319          */
2320         private static final String DEBUG_HANDSFREE_AUDIO = "debug.bt.hfp.audio";
2321 
2322         /** true allows incoming SCO connection out of call.
2323          */
2324         private static final String DEBUG_HANDSFREE_AUDIO_ANYTIME = "debug.bt.hfp.audio_anytime";
2325 
2326         /** Mock signal strength change in ASU - use 0 to 31 */
2327         private static final String DEBUG_HANDSFREE_SIGNAL = "debug.bt.hfp.signal";
2328 
2329         /** Debug AT+CLCC: print +CLCC result */
2330         private static final String DEBUG_HANDSFREE_CLCC = "debug.bt.hfp.clcc";
2331 
2332         /** Debug AT+BSIR - Send In Band Ringtones Unsolicited AT command.
2333          * debug.bt.unsol.inband = 0 => AT+BSIR = 0 sent by the AG
2334          * debug.bt.unsol.inband = 1 => AT+BSIR = 0 sent by the AG
2335          * Other values are ignored.
2336          */
2337 
2338         private static final String DEBUG_UNSOL_INBAND_RINGTONE =
2339             "debug.bt.unsol.inband";
2340 
2341         @Override
run()2342         public void run() {
2343             boolean oldService = true;
2344             boolean oldRoam = false;
2345             boolean oldAudio = false;
2346 
2347             while (!isInterrupted() && inDebug()) {
2348                 int batteryLevel = SystemProperties.getInt(DEBUG_HANDSFREE_BATTERY, -1);
2349                 if (batteryLevel >= 0 && batteryLevel <= 5) {
2350                     Intent intent = new Intent();
2351                     intent.putExtra("level", batteryLevel);
2352                     intent.putExtra("scale", 5);
2353                     mBluetoothPhoneState.updateBatteryState(intent);
2354                 }
2355 
2356                 boolean serviceStateChanged = false;
2357                 if (SystemProperties.getBoolean(DEBUG_HANDSFREE_SERVICE, true) != oldService) {
2358                     oldService = !oldService;
2359                     serviceStateChanged = true;
2360                 }
2361                 if (SystemProperties.getBoolean(DEBUG_HANDSFREE_ROAM, false) != oldRoam) {
2362                     oldRoam = !oldRoam;
2363                     serviceStateChanged = true;
2364                 }
2365                 if (serviceStateChanged) {
2366                     Bundle b = new Bundle();
2367                     b.putInt("state", oldService ? 0 : 1);
2368                     b.putBoolean("roaming", oldRoam);
2369                     mBluetoothPhoneState.updateServiceState(true, ServiceState.newFromBundle(b));
2370                 }
2371 
2372                 if (SystemProperties.getBoolean(DEBUG_HANDSFREE_AUDIO, false) != oldAudio) {
2373                     oldAudio = !oldAudio;
2374                     if (oldAudio) {
2375                         audioOn();
2376                     } else {
2377                         audioOff();
2378                     }
2379                 }
2380 
2381                 int signalLevel = SystemProperties.getInt(DEBUG_HANDSFREE_SIGNAL, -1);
2382                 if (signalLevel >= 0 && signalLevel <= 31) {
2383                     SignalStrength signalStrength = new SignalStrength(signalLevel, -1, -1, -1,
2384                             -1, -1, -1, true);
2385                     Intent intent = new Intent();
2386                     Bundle data = new Bundle();
2387                     signalStrength.fillInNotifierBundle(data);
2388                     intent.putExtras(data);
2389                     mBluetoothPhoneState.updateSignalState(intent);
2390                 }
2391 
2392                 if (SystemProperties.getBoolean(DEBUG_HANDSFREE_CLCC, false)) {
2393                     log(gsmGetClccResult().toString());
2394                 }
2395                 try {
2396                     sleep(1000);  // 1 second
2397                 } catch (InterruptedException e) {
2398                     break;
2399                 }
2400 
2401                 int inBandRing =
2402                     SystemProperties.getInt(DEBUG_UNSOL_INBAND_RINGTONE, -1);
2403                 if (inBandRing == 0 || inBandRing == 1) {
2404                     AtCommandResult result =
2405                         new AtCommandResult(AtCommandResult.UNSOLICITED);
2406                     result.addResponse("+BSIR: " + inBandRing);
2407                     sendURC(result.toString());
2408                 }
2409             }
2410         }
2411     }
2412 
cdmaSwapSecondCallState()2413     public void cdmaSwapSecondCallState() {
2414         if (VDBG) log("cdmaSetSecondCallState: Toggling mCdmaIsSecondCallActive");
2415         mCdmaIsSecondCallActive = !mCdmaIsSecondCallActive;
2416         mCdmaCallsSwapped = true;
2417     }
2418 
cdmaSetSecondCallState(boolean state)2419     public void cdmaSetSecondCallState(boolean state) {
2420         if (VDBG) log("cdmaSetSecondCallState: Setting mCdmaIsSecondCallActive to " + state);
2421         mCdmaIsSecondCallActive = state;
2422 
2423         if (!mCdmaIsSecondCallActive) {
2424             mCdmaCallsSwapped = false;
2425         }
2426     }
2427 
log(String msg)2428     private static void log(String msg) {
2429         Log.d(TAG, msg);
2430     }
2431 }
2432