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