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