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