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