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