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