1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.phone; 18 19 import android.bluetooth.AtCommandHandler; 20 import android.bluetooth.AtCommandResult; 21 import android.bluetooth.AtParser; 22 import android.bluetooth.BluetoothA2dp; 23 import android.bluetooth.BluetoothAdapter; 24 import android.bluetooth.BluetoothDevice; 25 import android.bluetooth.BluetoothHeadset; 26 import android.bluetooth.HeadsetBase; 27 import android.bluetooth.ScoSocket; 28 import android.content.ActivityNotFoundException; 29 import android.content.BroadcastReceiver; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.IntentFilter; 33 import android.media.AudioManager; 34 import android.net.Uri; 35 import android.os.AsyncResult; 36 import android.os.Bundle; 37 import android.os.Handler; 38 import android.os.Message; 39 import android.os.PowerManager; 40 import android.os.PowerManager.WakeLock; 41 import android.os.SystemProperties; 42 import android.telephony.PhoneNumberUtils; 43 import android.telephony.ServiceState; 44 import android.telephony.SignalStrength; 45 import android.util.Log; 46 47 import com.android.internal.telephony.Call; 48 import com.android.internal.telephony.Connection; 49 import com.android.internal.telephony.Phone; 50 import com.android.internal.telephony.TelephonyIntents; 51 52 import java.util.LinkedList; 53 54 /** 55 * Bluetooth headset manager for the Phone app. 56 * @hide 57 */ 58 public class BluetoothHandsfree { 59 private static final String TAG = "BT HS/HF"; 60 private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 1) 61 && (SystemProperties.getInt("ro.debuggable", 0) == 1); 62 private static final boolean VDBG = (PhoneApp.DBG_LEVEL >= 2); // even more logging 63 64 public static final int TYPE_UNKNOWN = 0; 65 public static final int TYPE_HEADSET = 1; 66 public static final int TYPE_HANDSFREE = 2; 67 68 private final Context mContext; 69 private final Phone mPhone; 70 private final BluetoothA2dp mA2dp; 71 72 private BluetoothDevice mA2dpDevice; 73 private int mA2dpState; 74 75 private ServiceState mServiceState; 76 private HeadsetBase mHeadset; // null when not connected 77 private int mHeadsetType; 78 private boolean mAudioPossible; 79 private ScoSocket mIncomingSco; 80 private ScoSocket mOutgoingSco; 81 private ScoSocket mConnectedSco; 82 83 private Call mForegroundCall; 84 private Call mBackgroundCall; 85 private Call mRingingCall; 86 87 private AudioManager mAudioManager; 88 private PowerManager mPowerManager; 89 90 private boolean mPendingSco; // waiting for a2dp sink to suspend before establishing SCO 91 private boolean mA2dpSuspended; 92 private boolean mUserWantsAudio; 93 private WakeLock mStartCallWakeLock; // held while waiting for the intent to start call 94 private WakeLock mStartVoiceRecognitionWakeLock; // held while waiting for voice recognition 95 96 // AT command state 97 private static final int GSM_MAX_CONNECTIONS = 6; // Max connections allowed by GSM 98 private static final int CDMA_MAX_CONNECTIONS = 2; // Max connections allowed by CDMA 99 100 private long mBgndEarliestConnectionTime = 0; 101 private boolean mClip = false; // Calling Line Information Presentation 102 private boolean mIndicatorsEnabled = false; 103 private boolean mCmee = false; // Extended Error reporting 104 private long[] mClccTimestamps; // Timestamps associated with each clcc index 105 private boolean[] mClccUsed; // Is this clcc index in use 106 private boolean mWaitingForCallStart; 107 private boolean mWaitingForVoiceRecognition; 108 // do not connect audio until service connection is established 109 // for 3-way supported devices, this is after AT+CHLD 110 // for non-3-way supported devices, this is after AT+CMER (see spec) 111 private boolean mServiceConnectionEstablished; 112 113 private final BluetoothPhoneState mBluetoothPhoneState; // for CIND and CIEV updates 114 private final BluetoothAtPhonebook mPhonebook; 115 private Phone.State mPhoneState = Phone.State.IDLE; 116 CdmaPhoneCallState.PhoneCallState mCdmaThreeWayCallState = 117 CdmaPhoneCallState.PhoneCallState.IDLE; 118 119 private DebugThread mDebugThread; 120 private int mScoGain = Integer.MIN_VALUE; 121 122 private static Intent sVoiceCommandIntent; 123 124 // Audio parameters 125 private static final String HEADSET_NREC = "bt_headset_nrec"; 126 private static final String HEADSET_NAME = "bt_headset_name"; 127 128 private int mRemoteBrsf = 0; 129 private int mLocalBrsf = 0; 130 131 // CDMA specific flag used in context with BT devices having display capabilities 132 // to show which Caller is active. This state might not be always true as in CDMA 133 // networks if a caller drops off no update is provided to the Phone. 134 // This flag is just used as a toggle to provide a update to the BT device to specify 135 // which caller is active. 136 private boolean mCdmaIsSecondCallActive = false; 137 138 /* Constants from Bluetooth Specification Hands-Free profile version 1.5 */ 139 private static final int BRSF_AG_THREE_WAY_CALLING = 1 << 0; 140 private static final int BRSF_AG_EC_NR = 1 << 1; 141 private static final int BRSF_AG_VOICE_RECOG = 1 << 2; 142 private static final int BRSF_AG_IN_BAND_RING = 1 << 3; 143 private static final int BRSF_AG_VOICE_TAG_NUMBE = 1 << 4; 144 private static final int BRSF_AG_REJECT_CALL = 1 << 5; 145 private static final int BRSF_AG_ENHANCED_CALL_STATUS = 1 << 6; 146 private static final int BRSF_AG_ENHANCED_CALL_CONTROL = 1 << 7; 147 private static final int BRSF_AG_ENHANCED_ERR_RESULT_CODES = 1 << 8; 148 149 private static final int BRSF_HF_EC_NR = 1 << 0; 150 private static final int BRSF_HF_CW_THREE_WAY_CALLING = 1 << 1; 151 private static final int BRSF_HF_CLIP = 1 << 2; 152 private static final int BRSF_HF_VOICE_REG_ACT = 1 << 3; 153 private static final int BRSF_HF_REMOTE_VOL_CONTROL = 1 << 4; 154 private static final int BRSF_HF_ENHANCED_CALL_STATUS = 1 << 5; 155 private static final int BRSF_HF_ENHANCED_CALL_CONTROL = 1 << 6; 156 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 */ 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 private 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 default: 701 mAudioPossible = false; 702 } 703 704 switch(mRingingCall.getState()) { 705 case INCOMING: 706 case WAITING: 707 callsetup = 1; 708 break; 709 } 710 711 switch(mBackgroundCall.getState()) { 712 case HOLDING: 713 if (call == 1) { 714 callheld = 1; 715 } else { 716 call = 1; 717 callheld = 2; 718 } 719 break; 720 } 721 722 if (mCall != call) { 723 if (call == 1) { 724 // This means that a call has transitioned from NOT ACTIVE to ACTIVE. 725 // Switch on audio. 726 audioOn(); 727 } 728 mCall = call; 729 if (sendUpdate) { 730 result.addResponse("+CIEV: 2," + mCall); 731 } 732 } 733 if (mCallsetup != callsetup) { 734 mCallsetup = callsetup; 735 if (sendUpdate) { 736 // If mCall = 0, send CIEV 737 // mCall = 1, mCallsetup = 0, send CIEV 738 // mCall = 1, mCallsetup = 1, send CIEV after CCWA, 739 // if 3 way supported. 740 // mCall = 1, mCallsetup = 2 / 3 -> send CIEV, 741 // if 3 way is supported 742 if (mCall != 1 || mCallsetup == 0 || 743 mCallsetup != 1 && (mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) { 744 result.addResponse("+CIEV: 3," + mCallsetup); 745 } 746 } 747 } 748 749 if (mPhone.getPhoneType() == Phone.PHONE_TYPE_CDMA) { 750 PhoneApp app = PhoneApp.getInstance(); 751 if (app.cdmaPhoneCallState != null) { 752 CdmaPhoneCallState.PhoneCallState currCdmaThreeWayCallState = 753 app.cdmaPhoneCallState.getCurrentCallState(); 754 CdmaPhoneCallState.PhoneCallState prevCdmaThreeWayCallState = 755 app.cdmaPhoneCallState.getPreviousCallState(); 756 757 callheld = getCdmaCallHeldStatus(currCdmaThreeWayCallState, 758 prevCdmaThreeWayCallState); 759 760 if (mCdmaThreeWayCallState != currCdmaThreeWayCallState) { 761 // In CDMA, the network does not provide any feedback 762 // to the phone when the 2nd MO call goes through the 763 // stages of DIALING > ALERTING -> ACTIVE we fake the 764 // sequence 765 if ((currCdmaThreeWayCallState == 766 CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) 767 && app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing()) { 768 mAudioPossible = true; 769 if (sendUpdate) { 770 if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) { 771 result.addResponse("+CIEV: 3,2"); 772 result.addResponse("+CIEV: 3,3"); 773 result.addResponse("+CIEV: 3,0"); 774 } 775 } 776 // We also need to send a Call started indication 777 // for cases where the 2nd MO was initiated was 778 // from a *BT hands free* and is waiting for a 779 // +BLND: OK response 780 callStarted(); 781 } 782 783 // In CDMA, the network does not provide any feedback to 784 // the phone when a user merges a 3way call or swaps 785 // between two calls we need to send a CIEV response 786 // indicating that a call state got changed which should 787 // trigger a CLCC update request from the BT client. 788 if (currCdmaThreeWayCallState == 789 CdmaPhoneCallState.PhoneCallState.CONF_CALL) { 790 mAudioPossible = true; 791 if (sendUpdate) { 792 if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) { 793 result.addResponse("+CIEV: 2,1"); 794 result.addResponse("+CIEV: 3,0"); 795 } 796 } 797 } 798 } 799 mCdmaThreeWayCallState = currCdmaThreeWayCallState; 800 } 801 } 802 803 boolean callsSwitched = 804 (callheld == 1 && ! (mBackgroundCall.getEarliestConnectTime() == 805 mBgndEarliestConnectionTime)); 806 807 mBgndEarliestConnectionTime = mBackgroundCall.getEarliestConnectTime(); 808 809 if (mCallheld != callheld || callsSwitched) { 810 mCallheld = callheld; 811 if (sendUpdate) { 812 result.addResponse("+CIEV: 4," + mCallheld); 813 } 814 } 815 816 if (callsetup == 1 && callsetup != prevCallsetup) { 817 // new incoming call 818 String number = null; 819 int type = 128; 820 // find incoming phone number and type 821 if (connection == null) { 822 connection = mRingingCall.getEarliestConnection(); 823 if (connection == null) { 824 Log.e(TAG, "Could not get a handle on Connection object for new " + 825 "incoming call"); 826 } 827 } 828 if (connection != null) { 829 number = connection.getAddress(); 830 if (number != null) { 831 type = PhoneNumberUtils.toaFromString(number); 832 } 833 } 834 if (number == null) { 835 number = ""; 836 } 837 if ((call != 0 || callheld != 0) && sendUpdate) { 838 // call waiting 839 if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) != 0x0) { 840 result.addResponse("+CCWA: \"" + number + "\"," + type); 841 result.addResponse("+CIEV: 3," + callsetup); 842 } 843 } else { 844 // regular new incoming call 845 mRingingNumber = number; 846 mRingingType = type; 847 mIgnoreRing = false; 848 mStopRing = false; 849 850 if ((mLocalBrsf & BRSF_AG_IN_BAND_RING) != 0x0) { 851 audioOn(); 852 } 853 result.addResult(ring()); 854 } 855 } 856 sendURC(result.toString()); 857 } 858 859 private int getCdmaCallHeldStatus(CdmaPhoneCallState.PhoneCallState currState, 860 CdmaPhoneCallState.PhoneCallState prevState) { 861 int callheld; 862 // Update the Call held information 863 if (currState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) { 864 if (prevState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) { 865 callheld = 0; //0: no calls held, as now *both* the caller are active 866 } else { 867 callheld = 1; //1: held call and active call, as on answering a 868 // Call Waiting, one of the caller *is* put on hold 869 } 870 } else if (currState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) { 871 callheld = 1; //1: held call and active call, as on make a 3 Way Call 872 // the first caller *is* put on hold 873 } else { 874 callheld = 0; //0: no calls held as this is a SINGLE_ACTIVE call 875 } 876 return callheld; 877 } 878 879 880 private AtCommandResult ring() { 881 if (!mIgnoreRing && !mStopRing && mRingingCall.isRinging()) { 882 AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED); 883 result.addResponse("RING"); 884 if (sendClipUpdate()) { 885 result.addResponse("+CLIP: \"" + mRingingNumber + "\"," + mRingingType); 886 } 887 888 Message msg = mStateChangeHandler.obtainMessage(RING); 889 mStateChangeHandler.sendMessageDelayed(msg, 3000); 890 return result; 891 } 892 return null; 893 } 894 895 private synchronized String toCregString() { 896 return new String("+CREG: 1," + mStat); 897 } 898 899 private synchronized AtCommandResult toCindResult() { 900 AtCommandResult result = new AtCommandResult(AtCommandResult.OK); 901 String status = "+CIND: " + mService + "," + mCall + "," + mCallsetup + "," + 902 mCallheld + "," + mSignal + "," + mRoam + "," + mBattchg; 903 result.addResponse(status); 904 return result; 905 } 906 907 private synchronized AtCommandResult toCsqResult() { 908 AtCommandResult result = new AtCommandResult(AtCommandResult.OK); 909 String status = "+CSQ: " + mRssi + ",99"; 910 result.addResponse(status); 911 return result; 912 } 913 914 915 private synchronized AtCommandResult getCindTestResult() { 916 return new AtCommandResult("+CIND: (\"service\",(0-1))," + "(\"call\",(0-1))," + 917 "(\"callsetup\",(0-3)),(\"callheld\",(0-2)),(\"signal\",(0-5))," + 918 "(\"roam\",(0-1)),(\"battchg\",(0-5))"); 919 } 920 921 private synchronized void ignoreRing() { 922 mCallsetup = 0; 923 mIgnoreRing = true; 924 if (sendUpdate()) { 925 sendURC("+CIEV: 3," + mCallsetup); 926 } 927 } 928 929 }; 930 931 private static final int SCO_ACCEPTED = 1; 932 private static final int SCO_CONNECTED = 2; 933 private static final int SCO_CLOSED = 3; 934 private static final int CHECK_CALL_STARTED = 4; 935 private static final int CHECK_VOICE_RECOGNITION_STARTED = 5; 936 private static final int MESSAGE_CHECK_PENDING_SCO = 6; 937 938 private final Handler mHandler = new Handler() { 939 @Override 940 public void handleMessage(Message msg) { 941 synchronized (BluetoothHandsfree.this) { 942 switch (msg.what) { 943 case SCO_ACCEPTED: 944 if (msg.arg1 == ScoSocket.STATE_CONNECTED) { 945 if (isHeadsetConnected() && (mAudioPossible || allowAudioAnytime()) && 946 mConnectedSco == null) { 947 Log.i(TAG, "Routing audio for incoming SCO connection"); 948 mConnectedSco = (ScoSocket)msg.obj; 949 mAudioManager.setBluetoothScoOn(true); 950 broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_CONNECTED, 951 mHeadset.getRemoteDevice()); 952 } else { 953 Log.i(TAG, "Rejecting incoming SCO connection"); 954 ((ScoSocket)msg.obj).close(); 955 } 956 } // else error trying to accept, try again 957 mIncomingSco = createScoSocket(); 958 mIncomingSco.accept(); 959 break; 960 case SCO_CONNECTED: 961 if (msg.arg1 == ScoSocket.STATE_CONNECTED && isHeadsetConnected() && 962 mConnectedSco == null) { 963 if (VDBG) log("Routing audio for outgoing SCO conection"); 964 mConnectedSco = (ScoSocket)msg.obj; 965 mAudioManager.setBluetoothScoOn(true); 966 broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_CONNECTED, 967 mHeadset.getRemoteDevice()); 968 } else if (msg.arg1 == ScoSocket.STATE_CONNECTED) { 969 if (VDBG) log("Rejecting new connected outgoing SCO socket"); 970 ((ScoSocket)msg.obj).close(); 971 mOutgoingSco.close(); 972 } 973 mOutgoingSco = null; 974 break; 975 case SCO_CLOSED: 976 if (mConnectedSco == (ScoSocket)msg.obj) { 977 mConnectedSco.close(); 978 mConnectedSco = null; 979 mAudioManager.setBluetoothScoOn(false); 980 broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_DISCONNECTED, 981 mHeadset.getRemoteDevice()); 982 } else if (mOutgoingSco == (ScoSocket)msg.obj) { 983 mOutgoingSco.close(); 984 mOutgoingSco = null; 985 } 986 break; 987 case CHECK_CALL_STARTED: 988 if (mWaitingForCallStart) { 989 mWaitingForCallStart = false; 990 Log.e(TAG, "Timeout waiting for call to start"); 991 sendURC("ERROR"); 992 if (mStartCallWakeLock.isHeld()) { 993 mStartCallWakeLock.release(); 994 } 995 } 996 break; 997 case CHECK_VOICE_RECOGNITION_STARTED: 998 if (mWaitingForVoiceRecognition) { 999 mWaitingForVoiceRecognition = false; 1000 Log.e(TAG, "Timeout waiting for voice recognition to start"); 1001 sendURC("ERROR"); 1002 } 1003 break; 1004 case MESSAGE_CHECK_PENDING_SCO: 1005 if (mPendingSco && isA2dpMultiProfile()) { 1006 Log.w(TAG, "Timeout suspending A2DP for SCO (mA2dpState = " + 1007 mA2dpState + "). Starting SCO anyway"); 1008 mOutgoingSco = createScoSocket(); 1009 if (!(isHeadsetConnected() && 1010 mOutgoingSco.connect(mHeadset.getRemoteDevice().getAddress(), 1011 mHeadset.getRemoteDevice().getName()))) { 1012 mOutgoingSco = null; 1013 } 1014 mPendingSco = false; 1015 } 1016 break; 1017 } 1018 } 1019 } 1020 }; 1021 1022 private ScoSocket createScoSocket() { 1023 return new ScoSocket(mPowerManager, mHandler, SCO_ACCEPTED, SCO_CONNECTED, SCO_CLOSED); 1024 } 1025 1026 private void broadcastAudioStateIntent(int state, BluetoothDevice device) { 1027 if (VDBG) log("broadcastAudioStateIntent(" + state + ")"); 1028 Intent intent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); 1029 intent.putExtra(BluetoothHeadset.EXTRA_AUDIO_STATE, state); 1030 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 1031 mContext.sendBroadcast(intent, android.Manifest.permission.BLUETOOTH); 1032 } 1033 1034 void updateBtHandsfreeAfterRadioTechnologyChange() { 1035 if(VDBG) Log.d(TAG, "updateBtHandsfreeAfterRadioTechnologyChange..."); 1036 1037 //Get the Call references from the new active phone again 1038 mRingingCall = mPhone.getRingingCall(); 1039 mForegroundCall = mPhone.getForegroundCall(); 1040 mBackgroundCall = mPhone.getBackgroundCall(); 1041 1042 mBluetoothPhoneState.updateBtPhoneStateAfterRadioTechnologyChange(); 1043 } 1044 1045 /** Request to establish SCO (audio) connection to bluetooth 1046 * headset/handsfree, if one is connected. Does not block. 1047 * Returns false if the user has requested audio off, or if there 1048 * is some other immediate problem that will prevent BT audio. 1049 */ 1050 /* package */ synchronized boolean audioOn() { 1051 if (VDBG) log("audioOn()"); 1052 if (!isHeadsetConnected()) { 1053 if (DBG) log("audioOn(): headset is not connected!"); 1054 return false; 1055 } 1056 if (mHeadsetType == TYPE_HANDSFREE && !mServiceConnectionEstablished) { 1057 if (DBG) log("audioOn(): service connection not yet established!"); 1058 return false; 1059 } 1060 1061 if (mConnectedSco != null) { 1062 if (DBG) log("audioOn(): audio is already connected"); 1063 return true; 1064 } 1065 1066 if (!mUserWantsAudio) { 1067 if (DBG) log("audioOn(): user requested no audio, ignoring"); 1068 return false; 1069 } 1070 1071 if (mOutgoingSco != null) { 1072 if (DBG) log("audioOn(): outgoing SCO already in progress"); 1073 return true; 1074 } 1075 1076 if (mPendingSco) { 1077 if (DBG) log("audioOn(): SCO already pending"); 1078 return true; 1079 } 1080 1081 mA2dpSuspended = false; 1082 mPendingSco = false; 1083 if (isA2dpMultiProfile() && mA2dpState == BluetoothA2dp.STATE_PLAYING) { 1084 if (DBG) log("suspending A2DP stream for SCO"); 1085 mA2dpSuspended = mA2dp.suspendSink(mA2dpDevice); 1086 if (mA2dpSuspended) { 1087 mPendingSco = true; 1088 Message msg = mHandler.obtainMessage(MESSAGE_CHECK_PENDING_SCO); 1089 mHandler.sendMessageDelayed(msg, 2000); 1090 } else { 1091 Log.w(TAG, "Could not suspend A2DP stream for SCO, going ahead with SCO"); 1092 } 1093 } 1094 1095 if (!mPendingSco) { 1096 mOutgoingSco = createScoSocket(); 1097 if (!mOutgoingSco.connect(mHeadset.getRemoteDevice().getAddress(), 1098 mHeadset.getRemoteDevice().getName())) { 1099 mOutgoingSco = null; 1100 } 1101 } 1102 1103 return true; 1104 } 1105 1106 /** Used to indicate the user requested BT audio on. 1107 * This will establish SCO (BT audio), even if the user requested it off 1108 * previously on this call. 1109 */ 1110 /* package */ synchronized void userWantsAudioOn() { 1111 mUserWantsAudio = true; 1112 audioOn(); 1113 } 1114 /** Used to indicate the user requested BT audio off. 1115 * This will prevent us from establishing BT audio again during this call 1116 * if audioOn() is called. 1117 */ 1118 /* package */ synchronized void userWantsAudioOff() { 1119 mUserWantsAudio = false; 1120 audioOff(); 1121 } 1122 1123 /** Request to disconnect SCO (audio) connection to bluetooth 1124 * headset/handsfree, if one is connected. Does not block. 1125 */ 1126 /* package */ synchronized void audioOff() { 1127 if (VDBG) log("audioOff(): mPendingSco: " + mPendingSco + 1128 ", mConnectedSco: " + mConnectedSco + 1129 ", mOutgoingSco: " + mOutgoingSco + 1130 ", mA2dpState: " + mA2dpState + 1131 ", mA2dpSuspended: " + mA2dpSuspended + 1132 ", mIncomingSco:" + mIncomingSco); 1133 1134 if (mA2dpSuspended) { 1135 if (isA2dpMultiProfile()) { 1136 if (DBG) log("resuming A2DP stream after disconnecting SCO"); 1137 mA2dp.resumeSink(mA2dpDevice); 1138 } 1139 mA2dpSuspended = false; 1140 } 1141 1142 mPendingSco = false; 1143 1144 if (mConnectedSco != null) { 1145 BluetoothDevice device = null; 1146 if (mHeadset != null) { 1147 device = mHeadset.getRemoteDevice(); 1148 } 1149 mConnectedSco.close(); 1150 mConnectedSco = null; 1151 mAudioManager.setBluetoothScoOn(false); 1152 broadcastAudioStateIntent(BluetoothHeadset.AUDIO_STATE_DISCONNECTED, device); 1153 } 1154 if (mOutgoingSco != null) { 1155 mOutgoingSco.close(); 1156 mOutgoingSco = null; 1157 } 1158 1159 } 1160 1161 /* package */ boolean isAudioOn() { 1162 return (mConnectedSco != null); 1163 } 1164 1165 private boolean isA2dpMultiProfile() { 1166 return mA2dp != null && mHeadset != null && mA2dpDevice != null && 1167 mA2dpDevice.equals(mHeadset.getRemoteDevice()); 1168 } 1169 1170 /* package */ void ignoreRing() { 1171 mBluetoothPhoneState.ignoreRing(); 1172 } 1173 1174 private void sendURC(String urc) { 1175 if (isHeadsetConnected()) { 1176 mHeadset.sendURC(urc); 1177 } 1178 } 1179 1180 /** helper to redial last dialled number */ 1181 private AtCommandResult redial() { 1182 String number = mPhonebook.getLastDialledNumber(); 1183 if (number == null) { 1184 // spec seems to suggest sending ERROR if we dont have a 1185 // number to redial 1186 if (VDBG) log("Bluetooth redial requested (+BLDN), but no previous " + 1187 "outgoing calls found. Ignoring"); 1188 return new AtCommandResult(AtCommandResult.ERROR); 1189 } 1190 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, 1191 Uri.fromParts("tel", number, null)); 1192 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1193 mContext.startActivity(intent); 1194 1195 // We do not immediately respond OK, wait until we get a phone state 1196 // update. If we return OK now and the handsfree immeidately requests 1197 // our phone state it will say we are not in call yet which confuses 1198 // some devices 1199 expectCallStart(); 1200 return new AtCommandResult(AtCommandResult.UNSOLICITED); // send nothing 1201 } 1202 1203 /** Build the +CLCC result 1204 * The complexity arises from the fact that we need to maintain the same 1205 * CLCC index even as a call moves between states. */ 1206 private synchronized AtCommandResult gsmGetClccResult() { 1207 // Collect all known connections 1208 Connection[] clccConnections = new Connection[GSM_MAX_CONNECTIONS]; // indexed by CLCC index 1209 LinkedList<Connection> newConnections = new LinkedList<Connection>(); 1210 LinkedList<Connection> connections = new LinkedList<Connection>(); 1211 if (mRingingCall.getState().isAlive()) { 1212 connections.addAll(mRingingCall.getConnections()); 1213 } 1214 if (mForegroundCall.getState().isAlive()) { 1215 connections.addAll(mForegroundCall.getConnections()); 1216 } 1217 if (mBackgroundCall.getState().isAlive()) { 1218 connections.addAll(mBackgroundCall.getConnections()); 1219 } 1220 1221 // Mark connections that we already known about 1222 boolean clccUsed[] = new boolean[GSM_MAX_CONNECTIONS]; 1223 for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) { 1224 clccUsed[i] = mClccUsed[i]; 1225 mClccUsed[i] = false; 1226 } 1227 for (Connection c : connections) { 1228 boolean found = false; 1229 long timestamp = c.getCreateTime(); 1230 for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) { 1231 if (clccUsed[i] && timestamp == mClccTimestamps[i]) { 1232 mClccUsed[i] = true; 1233 found = true; 1234 clccConnections[i] = c; 1235 break; 1236 } 1237 } 1238 if (!found) { 1239 newConnections.add(c); 1240 } 1241 } 1242 1243 // Find a CLCC index for new connections 1244 while (!newConnections.isEmpty()) { 1245 // Find lowest empty index 1246 int i = 0; 1247 while (mClccUsed[i]) i++; 1248 // Find earliest connection 1249 long earliestTimestamp = newConnections.get(0).getCreateTime(); 1250 Connection earliestConnection = newConnections.get(0); 1251 for (int j = 0; j < newConnections.size(); j++) { 1252 long timestamp = newConnections.get(j).getCreateTime(); 1253 if (timestamp < earliestTimestamp) { 1254 earliestTimestamp = timestamp; 1255 earliestConnection = newConnections.get(j); 1256 } 1257 } 1258 1259 // update 1260 mClccUsed[i] = true; 1261 mClccTimestamps[i] = earliestTimestamp; 1262 clccConnections[i] = earliestConnection; 1263 newConnections.remove(earliestConnection); 1264 } 1265 1266 // Build CLCC 1267 AtCommandResult result = new AtCommandResult(AtCommandResult.OK); 1268 for (int i = 0; i < clccConnections.length; i++) { 1269 if (mClccUsed[i]) { 1270 String clccEntry = connectionToClccEntry(i, clccConnections[i]); 1271 if (clccEntry != null) { 1272 result.addResponse(clccEntry); 1273 } 1274 } 1275 } 1276 1277 return result; 1278 } 1279 1280 /** Convert a Connection object into a single +CLCC result */ 1281 private String connectionToClccEntry(int index, Connection c) { 1282 int state; 1283 switch (c.getState()) { 1284 case ACTIVE: 1285 state = 0; 1286 break; 1287 case HOLDING: 1288 state = 1; 1289 break; 1290 case DIALING: 1291 state = 2; 1292 break; 1293 case ALERTING: 1294 state = 3; 1295 break; 1296 case INCOMING: 1297 state = 4; 1298 break; 1299 case WAITING: 1300 state = 5; 1301 break; 1302 default: 1303 return null; // bad state 1304 } 1305 1306 int mpty = 0; 1307 Call call = c.getCall(); 1308 if (call != null) { 1309 mpty = call.isMultiparty() ? 1 : 0; 1310 } 1311 1312 int direction = c.isIncoming() ? 1 : 0; 1313 1314 String number = c.getAddress(); 1315 int type = -1; 1316 if (number != null) { 1317 type = PhoneNumberUtils.toaFromString(number); 1318 } 1319 1320 String result = "+CLCC: " + (index + 1) + "," + direction + "," + state + ",0," + mpty; 1321 if (number != null) { 1322 result += ",\"" + number + "\"," + type; 1323 } 1324 return result; 1325 } 1326 1327 /** Build the +CLCC result for CDMA 1328 * The complexity arises from the fact that we need to maintain the same 1329 * CLCC index even as a call moves between states. */ 1330 private synchronized AtCommandResult cdmaGetClccResult() { 1331 // In CDMA at one time a user can have only two live/active connections 1332 Connection[] clccConnections = new Connection[CDMA_MAX_CONNECTIONS];// indexed by CLCC index 1333 1334 Call.State ringingCallState = mRingingCall.getState(); 1335 // If the Ringing Call state is INCOMING, that means this is the very first call 1336 // hence there should not be any Foreground Call 1337 if (ringingCallState == Call.State.INCOMING) { 1338 if (VDBG) log("Filling clccConnections[0] for INCOMING state"); 1339 clccConnections[0] = mRingingCall.getLatestConnection(); 1340 } else if (mForegroundCall.getState().isAlive()) { 1341 // Getting Foreground Call connection based on Call state 1342 if (mRingingCall.isRinging()) { 1343 if (VDBG) log("Filling clccConnections[0] & [1] for CALL WAITING state"); 1344 clccConnections[0] = mForegroundCall.getEarliestConnection(); 1345 clccConnections[1] = mRingingCall.getLatestConnection(); 1346 } else { 1347 if (mForegroundCall.getConnections().size() <= 1) { 1348 // Single call scenario 1349 if (VDBG) log("Filling clccConnections[0] with ForgroundCall latest connection"); 1350 clccConnections[0] = mForegroundCall.getLatestConnection(); 1351 } else { 1352 // Multiple Call scenario. This would be true for both 1353 // CONF_CALL and THRWAY_ACTIVE state 1354 if (VDBG) log("Filling clccConnections[0] & [1] with ForgroundCall connections"); 1355 clccConnections[0] = mForegroundCall.getEarliestConnection(); 1356 clccConnections[1] = mForegroundCall.getLatestConnection(); 1357 } 1358 } 1359 } 1360 1361 // Update the mCdmaIsSecondCallActive flag based on the Phone call state 1362 if (PhoneApp.getInstance().cdmaPhoneCallState.getCurrentCallState() 1363 == CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE) { 1364 cdmaSetSecondCallState(false); 1365 } else if (PhoneApp.getInstance().cdmaPhoneCallState.getCurrentCallState() 1366 == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) { 1367 cdmaSetSecondCallState(true); 1368 } 1369 1370 // Build CLCC 1371 AtCommandResult result = new AtCommandResult(AtCommandResult.OK); 1372 for (int i = 0; (i < clccConnections.length) && (clccConnections[i] != null); i++) { 1373 String clccEntry = cdmaConnectionToClccEntry(i, clccConnections[i]); 1374 if (clccEntry != null) { 1375 result.addResponse(clccEntry); 1376 } 1377 } 1378 1379 return result; 1380 } 1381 1382 /** Convert a Connection object into a single +CLCC result for CDMA phones */ 1383 private String cdmaConnectionToClccEntry(int index, Connection c) { 1384 int state; 1385 PhoneApp app = PhoneApp.getInstance(); 1386 CdmaPhoneCallState.PhoneCallState currCdmaCallState = 1387 app.cdmaPhoneCallState.getCurrentCallState(); 1388 CdmaPhoneCallState.PhoneCallState prevCdmaCallState = 1389 app.cdmaPhoneCallState.getPreviousCallState(); 1390 1391 if ((prevCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) 1392 && (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL)) { 1393 // If the current state is reached after merging two calls 1394 // we set the state of all the connections as ACTIVE 1395 state = 0; 1396 } else { 1397 switch (c.getState()) { 1398 case ACTIVE: 1399 // For CDMA since both the connections are set as active by FW after accepting 1400 // a Call waiting or making a 3 way call, we need to set the state specifically 1401 // to ACTIVE/HOLDING based on the mCdmaIsSecondCallActive flag. This way the 1402 // CLCC result will allow BT devices to enable the swap or merge options 1403 if (index == 0) { // For the 1st active connection 1404 state = mCdmaIsSecondCallActive ? 1 : 0; 1405 } else { // for the 2nd active connection 1406 state = mCdmaIsSecondCallActive ? 0 : 1; 1407 } 1408 break; 1409 case HOLDING: 1410 state = 1; 1411 break; 1412 case DIALING: 1413 state = 2; 1414 break; 1415 case ALERTING: 1416 state = 3; 1417 break; 1418 case INCOMING: 1419 state = 4; 1420 break; 1421 case WAITING: 1422 state = 5; 1423 break; 1424 default: 1425 return null; // bad state 1426 } 1427 } 1428 1429 int mpty = 0; 1430 if (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) { 1431 mpty = 1; 1432 } else { 1433 mpty = 0; 1434 } 1435 1436 int direction = c.isIncoming() ? 1 : 0; 1437 1438 String number = c.getAddress(); 1439 int type = -1; 1440 if (number != null) { 1441 type = PhoneNumberUtils.toaFromString(number); 1442 } 1443 1444 String result = "+CLCC: " + (index + 1) + "," + direction + "," + state + ",0," + mpty; 1445 if (number != null) { 1446 result += ",\"" + number + "\"," + type; 1447 } 1448 return result; 1449 } 1450 1451 /** 1452 * Register AT Command handlers to implement the Headset profile 1453 */ 1454 private void initializeHeadsetAtParser() { 1455 if (VDBG) log("Registering Headset AT commands"); 1456 AtParser parser = mHeadset.getAtParser(); 1457 // Headset's usually only have one button, which is meant to cause the 1458 // HS to send us AT+CKPD=200 or AT+CKPD. 1459 parser.register("+CKPD", new AtCommandHandler() { 1460 private AtCommandResult headsetButtonPress() { 1461 if (mRingingCall.isRinging()) { 1462 // Answer the call 1463 mBluetoothPhoneState.stopRing(); 1464 sendURC("OK"); 1465 PhoneUtils.answerCall(mPhone); 1466 // If in-band ring tone is supported, SCO connection will already 1467 // be up and the following call will just return. 1468 audioOn(); 1469 return new AtCommandResult(AtCommandResult.UNSOLICITED); 1470 } else if (mForegroundCall.getState().isAlive()) { 1471 if (!isAudioOn()) { 1472 // Transfer audio from AG to HS 1473 audioOn(); 1474 } else { 1475 if (mHeadset.getDirection() == HeadsetBase.DIRECTION_INCOMING && 1476 (System.currentTimeMillis() - mHeadset.getConnectTimestamp()) < 5000) { 1477 // Headset made a recent ACL connection to us - and 1478 // made a mandatory AT+CKPD request to connect 1479 // audio which races with our automatic audio 1480 // setup. ignore 1481 } else { 1482 // Hang up the call 1483 audioOff(); 1484 PhoneUtils.hangup(mPhone); 1485 } 1486 } 1487 return new AtCommandResult(AtCommandResult.OK); 1488 } else { 1489 // No current call - redial last number 1490 return redial(); 1491 } 1492 } 1493 @Override 1494 public AtCommandResult handleActionCommand() { 1495 return headsetButtonPress(); 1496 } 1497 @Override 1498 public AtCommandResult handleSetCommand(Object[] args) { 1499 return headsetButtonPress(); 1500 } 1501 }); 1502 } 1503 1504 /** 1505 * Register AT Command handlers to implement the Handsfree profile 1506 */ 1507 private void initializeHandsfreeAtParser() { 1508 if (VDBG) log("Registering Handsfree AT commands"); 1509 AtParser parser = mHeadset.getAtParser(); 1510 1511 // Answer 1512 parser.register('A', new AtCommandHandler() { 1513 @Override 1514 public AtCommandResult handleBasicCommand(String args) { 1515 sendURC("OK"); 1516 mBluetoothPhoneState.stopRing(); 1517 PhoneUtils.answerCall(mPhone); 1518 return new AtCommandResult(AtCommandResult.UNSOLICITED); 1519 } 1520 }); 1521 parser.register('D', new AtCommandHandler() { 1522 @Override 1523 public AtCommandResult handleBasicCommand(String args) { 1524 if (args.length() > 0) { 1525 if (args.charAt(0) == '>') { 1526 // Yuck - memory dialling requested. 1527 // Just dial last number for now 1528 if (args.startsWith(">9999")) { // for PTS test 1529 return new AtCommandResult(AtCommandResult.ERROR); 1530 } 1531 return redial(); 1532 } else { 1533 // Remove trailing ';' 1534 if (args.charAt(args.length() - 1) == ';') { 1535 args = args.substring(0, args.length() - 1); 1536 } 1537 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, 1538 Uri.fromParts("tel", args, null)); 1539 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1540 mContext.startActivity(intent); 1541 1542 expectCallStart(); 1543 return new AtCommandResult(AtCommandResult.UNSOLICITED); // send nothing 1544 } 1545 } 1546 return new AtCommandResult(AtCommandResult.ERROR); 1547 } 1548 }); 1549 1550 // Hang-up command 1551 parser.register("+CHUP", new AtCommandHandler() { 1552 @Override 1553 public AtCommandResult handleActionCommand() { 1554 sendURC("OK"); 1555 if (!mForegroundCall.isIdle()) { 1556 PhoneUtils.hangupActiveCall(mPhone); 1557 } else if (!mRingingCall.isIdle()) { 1558 PhoneUtils.hangupRingingCall(mPhone); 1559 } else if (!mBackgroundCall.isIdle()) { 1560 PhoneUtils.hangupHoldingCall(mPhone); 1561 } 1562 return new AtCommandResult(AtCommandResult.UNSOLICITED); 1563 } 1564 }); 1565 1566 // Bluetooth Retrieve Supported Features command 1567 parser.register("+BRSF", new AtCommandHandler() { 1568 private AtCommandResult sendBRSF() { 1569 return new AtCommandResult("+BRSF: " + mLocalBrsf); 1570 } 1571 @Override 1572 public AtCommandResult handleSetCommand(Object[] args) { 1573 // AT+BRSF=<handsfree supported features bitmap> 1574 // Handsfree is telling us which features it supports. We 1575 // send the features we support 1576 if (args.length == 1 && (args[0] instanceof Integer)) { 1577 mRemoteBrsf = (Integer) args[0]; 1578 } else { 1579 Log.w(TAG, "HF didn't sent BRSF assuming 0"); 1580 } 1581 return sendBRSF(); 1582 } 1583 @Override 1584 public AtCommandResult handleActionCommand() { 1585 // This seems to be out of spec, but lets do the nice thing 1586 return sendBRSF(); 1587 } 1588 @Override 1589 public AtCommandResult handleReadCommand() { 1590 // This seems to be out of spec, but lets do the nice thing 1591 return sendBRSF(); 1592 } 1593 }); 1594 1595 // Call waiting notification on/off 1596 parser.register("+CCWA", new AtCommandHandler() { 1597 @Override 1598 public AtCommandResult handleActionCommand() { 1599 // Seems to be out of spec, but lets return nicely 1600 return new AtCommandResult(AtCommandResult.OK); 1601 } 1602 @Override 1603 public AtCommandResult handleReadCommand() { 1604 // Call waiting is always on 1605 return new AtCommandResult("+CCWA: 1"); 1606 } 1607 @Override 1608 public AtCommandResult handleSetCommand(Object[] args) { 1609 // AT+CCWA=<n> 1610 // Handsfree is trying to enable/disable call waiting. We 1611 // cannot disable in the current implementation. 1612 return new AtCommandResult(AtCommandResult.OK); 1613 } 1614 @Override 1615 public AtCommandResult handleTestCommand() { 1616 // Request for range of supported CCWA paramters 1617 return new AtCommandResult("+CCWA: (\"n\",(1))"); 1618 } 1619 }); 1620 1621 // Mobile Equipment Event Reporting enable/disable command 1622 // Of the full 3GPP syntax paramters (mode, keyp, disp, ind, bfr) we 1623 // only support paramter ind (disable/enable evert reporting using 1624 // +CDEV) 1625 parser.register("+CMER", new AtCommandHandler() { 1626 @Override 1627 public AtCommandResult handleReadCommand() { 1628 return new AtCommandResult( 1629 "+CMER: 3,0,0," + (mIndicatorsEnabled ? "1" : "0")); 1630 } 1631 @Override 1632 public AtCommandResult handleSetCommand(Object[] args) { 1633 if (args.length < 4) { 1634 // This is a syntax error 1635 return new AtCommandResult(AtCommandResult.ERROR); 1636 } else if (args[0].equals(3) && args[1].equals(0) && 1637 args[2].equals(0)) { 1638 boolean valid = false; 1639 if (args[3].equals(0)) { 1640 mIndicatorsEnabled = false; 1641 valid = true; 1642 } else if (args[3].equals(1)) { 1643 mIndicatorsEnabled = true; 1644 valid = true; 1645 } 1646 if (valid) { 1647 if ((mRemoteBrsf & BRSF_HF_CW_THREE_WAY_CALLING) == 0x0) { 1648 mServiceConnectionEstablished = true; 1649 sendURC("OK"); // send immediately, then initiate audio 1650 if (isIncallAudio()) { 1651 audioOn(); 1652 } 1653 // only send OK once 1654 return new AtCommandResult(AtCommandResult.UNSOLICITED); 1655 } else { 1656 return new AtCommandResult(AtCommandResult.OK); 1657 } 1658 } 1659 } 1660 return reportCmeError(BluetoothCmeError.OPERATION_NOT_SUPPORTED); 1661 } 1662 @Override 1663 public AtCommandResult handleTestCommand() { 1664 return new AtCommandResult("+CMER: (3),(0),(0),(0-1)"); 1665 } 1666 }); 1667 1668 // Mobile Equipment Error Reporting enable/disable 1669 parser.register("+CMEE", new AtCommandHandler() { 1670 @Override 1671 public AtCommandResult handleActionCommand() { 1672 // out of spec, assume they want to enable 1673 mCmee = true; 1674 return new AtCommandResult(AtCommandResult.OK); 1675 } 1676 @Override 1677 public AtCommandResult handleReadCommand() { 1678 return new AtCommandResult("+CMEE: " + (mCmee ? "1" : "0")); 1679 } 1680 @Override 1681 public AtCommandResult handleSetCommand(Object[] args) { 1682 // AT+CMEE=<n> 1683 if (args.length == 0) { 1684 // <n> ommitted - default to 0 1685 mCmee = false; 1686 return new AtCommandResult(AtCommandResult.OK); 1687 } else if (!(args[0] instanceof Integer)) { 1688 // Syntax error 1689 return new AtCommandResult(AtCommandResult.ERROR); 1690 } else { 1691 mCmee = ((Integer)args[0] == 1); 1692 return new AtCommandResult(AtCommandResult.OK); 1693 } 1694 } 1695 @Override 1696 public AtCommandResult handleTestCommand() { 1697 // Probably not required but spec, but no harm done 1698 return new AtCommandResult("+CMEE: (0-1)"); 1699 } 1700 }); 1701 1702 // Bluetooth Last Dialled Number 1703 parser.register("+BLDN", new AtCommandHandler() { 1704 @Override 1705 public AtCommandResult handleActionCommand() { 1706 return redial(); 1707 } 1708 }); 1709 1710 // Indicator Update command 1711 parser.register("+CIND", new AtCommandHandler() { 1712 @Override 1713 public AtCommandResult handleReadCommand() { 1714 return mBluetoothPhoneState.toCindResult(); 1715 } 1716 @Override 1717 public AtCommandResult handleTestCommand() { 1718 return mBluetoothPhoneState.getCindTestResult(); 1719 } 1720 }); 1721 1722 // Query Signal Quality (legacy) 1723 parser.register("+CSQ", new AtCommandHandler() { 1724 @Override 1725 public AtCommandResult handleActionCommand() { 1726 return mBluetoothPhoneState.toCsqResult(); 1727 } 1728 }); 1729 1730 // Query network registration state 1731 parser.register("+CREG", new AtCommandHandler() { 1732 @Override 1733 public AtCommandResult handleReadCommand() { 1734 return new AtCommandResult(mBluetoothPhoneState.toCregString()); 1735 } 1736 }); 1737 1738 // Send DTMF. I don't know if we are also expected to play the DTMF tone 1739 // locally, right now we don't 1740 parser.register("+VTS", new AtCommandHandler() { 1741 @Override 1742 public AtCommandResult handleSetCommand(Object[] args) { 1743 if (args.length >= 1) { 1744 char c; 1745 if (args[0] instanceof Integer) { 1746 c = ((Integer) args[0]).toString().charAt(0); 1747 } else { 1748 c = ((String) args[0]).charAt(0); 1749 } 1750 if (isValidDtmf(c)) { 1751 mPhone.sendDtmf(c); 1752 return new AtCommandResult(AtCommandResult.OK); 1753 } 1754 } 1755 return new AtCommandResult(AtCommandResult.ERROR); 1756 } 1757 private boolean isValidDtmf(char c) { 1758 switch (c) { 1759 case '#': 1760 case '*': 1761 return true; 1762 default: 1763 if (Character.digit(c, 14) != -1) { 1764 return true; // 0-9 and A-D 1765 } 1766 return false; 1767 } 1768 } 1769 }); 1770 1771 // List calls 1772 parser.register("+CLCC", new AtCommandHandler() { 1773 @Override 1774 public AtCommandResult handleActionCommand() { 1775 int phoneType = mPhone.getPhoneType(); 1776 if (phoneType == Phone.PHONE_TYPE_CDMA) { 1777 return cdmaGetClccResult(); 1778 } else if (phoneType == Phone.PHONE_TYPE_GSM) { 1779 return gsmGetClccResult(); 1780 } else { 1781 throw new IllegalStateException("Unexpected phone type: " + phoneType); 1782 } 1783 } 1784 }); 1785 1786 // Call Hold and Multiparty Handling command 1787 parser.register("+CHLD", new AtCommandHandler() { 1788 @Override 1789 public AtCommandResult handleSetCommand(Object[] args) { 1790 int phoneType = mPhone.getPhoneType(); 1791 if (args.length >= 1) { 1792 if (args[0].equals(0)) { 1793 boolean result; 1794 if (mRingingCall.isRinging()) { 1795 result = PhoneUtils.hangupRingingCall(mPhone); 1796 } else { 1797 result = PhoneUtils.hangupHoldingCall(mPhone); 1798 } 1799 if (result) { 1800 return new AtCommandResult(AtCommandResult.OK); 1801 } else { 1802 return new AtCommandResult(AtCommandResult.ERROR); 1803 } 1804 } else if (args[0].equals(1)) { 1805 if (phoneType == Phone.PHONE_TYPE_CDMA) { 1806 if (mRingingCall.isRinging()) { 1807 // If there is Call waiting then answer the call and 1808 // put the first call on hold. 1809 if (VDBG) log("CHLD:1 Callwaiting Answer call"); 1810 PhoneUtils.answerCall(mPhone); 1811 PhoneUtils.setMute(mPhone, false); 1812 // Setting the second callers state flag to TRUE (i.e. active) 1813 cdmaSetSecondCallState(true); 1814 } else { 1815 // If there is no Call waiting then just hangup 1816 // the active call. In CDMA this mean that the complete 1817 // call session would be ended 1818 if (VDBG) log("CHLD:1 Hangup Call"); 1819 PhoneUtils.hangup(mPhone); 1820 } 1821 return new AtCommandResult(AtCommandResult.OK); 1822 } else if (phoneType == Phone.PHONE_TYPE_GSM) { 1823 // Hangup active call, answer held call 1824 if (PhoneUtils.answerAndEndActive(mPhone)) { 1825 return new AtCommandResult(AtCommandResult.OK); 1826 } else { 1827 return new AtCommandResult(AtCommandResult.ERROR); 1828 } 1829 } else { 1830 throw new IllegalStateException("Unexpected phone type: " + phoneType); 1831 } 1832 } else if (args[0].equals(2)) { 1833 if (phoneType == Phone.PHONE_TYPE_CDMA) { 1834 // For CDMA, the way we switch to a new incoming call is by 1835 // calling PhoneUtils.answerCall(). switchAndHoldActive() won't 1836 // properly update the call state within telephony. 1837 // If the Phone state is already in CONF_CALL then we simply send 1838 // a flash cmd by calling switchHoldingAndActive() 1839 if (mRingingCall.isRinging()) { 1840 if (VDBG) log("CHLD:2 Callwaiting Answer call"); 1841 PhoneUtils.answerCall(mPhone); 1842 PhoneUtils.setMute(mPhone, false); 1843 // Setting the second callers state flag to TRUE (i.e. active) 1844 cdmaSetSecondCallState(true); 1845 } else if (PhoneApp.getInstance().cdmaPhoneCallState 1846 .getCurrentCallState() 1847 == CdmaPhoneCallState.PhoneCallState.CONF_CALL) { 1848 if (VDBG) log("CHLD:2 Swap Calls"); 1849 PhoneUtils.switchHoldingAndActive(mPhone); 1850 // Toggle the second callers active state flag 1851 cdmaSwapSecondCallState(); 1852 } 1853 } else if (phoneType == Phone.PHONE_TYPE_GSM) { 1854 PhoneUtils.switchHoldingAndActive(mPhone); 1855 } else { 1856 throw new IllegalStateException("Unexpected phone type: " + phoneType); 1857 } 1858 return new AtCommandResult(AtCommandResult.OK); 1859 } else if (args[0].equals(3)) { 1860 if (phoneType == Phone.PHONE_TYPE_CDMA) { 1861 // For CDMA, we need to check if the call is in THRWAY_ACTIVE state 1862 if (PhoneApp.getInstance().cdmaPhoneCallState.getCurrentCallState() 1863 == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) { 1864 if (VDBG) log("CHLD:3 Merge Calls"); 1865 PhoneUtils.mergeCalls(mPhone); 1866 } 1867 } else if (phoneType == Phone.PHONE_TYPE_GSM) { 1868 if (mForegroundCall.getState().isAlive() && 1869 mBackgroundCall.getState().isAlive()) { 1870 PhoneUtils.mergeCalls(mPhone); 1871 } 1872 } else { 1873 throw new IllegalStateException("Unexpected phone type: " + phoneType); 1874 } 1875 return new AtCommandResult(AtCommandResult.OK); 1876 } 1877 } 1878 return new AtCommandResult(AtCommandResult.ERROR); 1879 } 1880 @Override 1881 public AtCommandResult handleTestCommand() { 1882 mServiceConnectionEstablished = true; 1883 sendURC("+CHLD: (0,1,2,3)"); 1884 sendURC("OK"); // send reply first, then connect audio 1885 if (isIncallAudio()) { 1886 audioOn(); 1887 } 1888 // already replied 1889 return new AtCommandResult(AtCommandResult.UNSOLICITED); 1890 } 1891 }); 1892 1893 // Get Network operator name 1894 parser.register("+COPS", new AtCommandHandler() { 1895 @Override 1896 public AtCommandResult handleReadCommand() { 1897 String operatorName = mPhone.getServiceState().getOperatorAlphaLong(); 1898 if (operatorName != null) { 1899 if (operatorName.length() > 16) { 1900 operatorName = operatorName.substring(0, 16); 1901 } 1902 return new AtCommandResult( 1903 "+COPS: 0,0,\"" + operatorName + "\""); 1904 } else { 1905 return new AtCommandResult( 1906 "+COPS: 0,0,\"UNKNOWN\",0"); 1907 } 1908 } 1909 @Override 1910 public AtCommandResult handleSetCommand(Object[] args) { 1911 // Handsfree only supports AT+COPS=3,0 1912 if (args.length != 2 || !(args[0] instanceof Integer) 1913 || !(args[1] instanceof Integer)) { 1914 // syntax error 1915 return new AtCommandResult(AtCommandResult.ERROR); 1916 } else if ((Integer)args[0] != 3 || (Integer)args[1] != 0) { 1917 return reportCmeError(BluetoothCmeError.OPERATION_NOT_SUPPORTED); 1918 } else { 1919 return new AtCommandResult(AtCommandResult.OK); 1920 } 1921 } 1922 @Override 1923 public AtCommandResult handleTestCommand() { 1924 // Out of spec, but lets be friendly 1925 return new AtCommandResult("+COPS: (3),(0)"); 1926 } 1927 }); 1928 1929 // Mobile PIN 1930 // AT+CPIN is not in the handsfree spec (although it is in 3GPP) 1931 parser.register("+CPIN", new AtCommandHandler() { 1932 @Override 1933 public AtCommandResult handleReadCommand() { 1934 return new AtCommandResult("+CPIN: READY"); 1935 } 1936 }); 1937 1938 // Bluetooth Response and Hold 1939 // Only supported on PDC (Japan) and CDMA networks. 1940 parser.register("+BTRH", new AtCommandHandler() { 1941 @Override 1942 public AtCommandResult handleReadCommand() { 1943 // Replying with just OK indicates no response and hold 1944 // features in use now 1945 return new AtCommandResult(AtCommandResult.OK); 1946 } 1947 @Override 1948 public AtCommandResult handleSetCommand(Object[] args) { 1949 // Neeed PDC or CDMA 1950 return new AtCommandResult(AtCommandResult.ERROR); 1951 } 1952 }); 1953 1954 // Request International Mobile Subscriber Identity (IMSI) 1955 // Not in bluetooth handset spec 1956 parser.register("+CIMI", new AtCommandHandler() { 1957 @Override 1958 public AtCommandResult handleActionCommand() { 1959 // AT+CIMI 1960 String imsi = mPhone.getSubscriberId(); 1961 if (imsi == null || imsi.length() == 0) { 1962 return reportCmeError(BluetoothCmeError.SIM_FAILURE); 1963 } else { 1964 return new AtCommandResult(imsi); 1965 } 1966 } 1967 }); 1968 1969 // Calling Line Identification Presentation 1970 parser.register("+CLIP", new AtCommandHandler() { 1971 @Override 1972 public AtCommandResult handleReadCommand() { 1973 // Currently assumes the network is provisioned for CLIP 1974 return new AtCommandResult("+CLIP: " + (mClip ? "1" : "0") + ",1"); 1975 } 1976 @Override 1977 public AtCommandResult handleSetCommand(Object[] args) { 1978 // AT+CLIP=<n> 1979 if (args.length >= 1 && (args[0].equals(0) || args[0].equals(1))) { 1980 mClip = args[0].equals(1); 1981 return new AtCommandResult(AtCommandResult.OK); 1982 } else { 1983 return new AtCommandResult(AtCommandResult.ERROR); 1984 } 1985 } 1986 @Override 1987 public AtCommandResult handleTestCommand() { 1988 return new AtCommandResult("+CLIP: (0-1)"); 1989 } 1990 }); 1991 1992 // AT+CGSN - Returns the device IMEI number. 1993 parser.register("+CGSN", new AtCommandHandler() { 1994 @Override 1995 public AtCommandResult handleActionCommand() { 1996 // Get the IMEI of the device. 1997 // mPhone will not be NULL at this point. 1998 return new AtCommandResult("+CGSN: " + mPhone.getDeviceId()); 1999 } 2000 }); 2001 2002 // AT+CGMM - Query Model Information 2003 parser.register("+CGMM", new AtCommandHandler() { 2004 @Override 2005 public AtCommandResult handleActionCommand() { 2006 // Return the Model Information. 2007 String model = SystemProperties.get("ro.product.model"); 2008 if (model != null) { 2009 return new AtCommandResult("+CGMM: " + model); 2010 } else { 2011 return new AtCommandResult(AtCommandResult.ERROR); 2012 } 2013 } 2014 }); 2015 2016 // AT+CGMI - Query Manufacturer Information 2017 parser.register("+CGMI", new AtCommandHandler() { 2018 @Override 2019 public AtCommandResult handleActionCommand() { 2020 // Return the Model Information. 2021 String manuf = SystemProperties.get("ro.product.manufacturer"); 2022 if (manuf != null) { 2023 return new AtCommandResult("+CGMI: " + manuf); 2024 } else { 2025 return new AtCommandResult(AtCommandResult.ERROR); 2026 } 2027 } 2028 }); 2029 2030 // Noise Reduction and Echo Cancellation control 2031 parser.register("+NREC", new AtCommandHandler() { 2032 @Override 2033 public AtCommandResult handleSetCommand(Object[] args) { 2034 if (args[0].equals(0)) { 2035 mAudioManager.setParameters(HEADSET_NREC+"=off"); 2036 return new AtCommandResult(AtCommandResult.OK); 2037 } else if (args[0].equals(1)) { 2038 mAudioManager.setParameters(HEADSET_NREC+"=on"); 2039 return new AtCommandResult(AtCommandResult.OK); 2040 } 2041 return new AtCommandResult(AtCommandResult.ERROR); 2042 } 2043 }); 2044 2045 // Voice recognition (dialing) 2046 parser.register("+BVRA", new AtCommandHandler() { 2047 @Override 2048 public AtCommandResult handleSetCommand(Object[] args) { 2049 if (!BluetoothHeadset.isBluetoothVoiceDialingEnabled(mContext)) { 2050 return new AtCommandResult(AtCommandResult.ERROR); 2051 } 2052 if (args.length >= 1 && args[0].equals(1)) { 2053 synchronized (BluetoothHandsfree.this) { 2054 if (!mWaitingForVoiceRecognition) { 2055 try { 2056 mContext.startActivity(sVoiceCommandIntent); 2057 } catch (ActivityNotFoundException e) { 2058 return new AtCommandResult(AtCommandResult.ERROR); 2059 } 2060 expectVoiceRecognition(); 2061 } 2062 } 2063 return new AtCommandResult(AtCommandResult.UNSOLICITED); // send nothing yet 2064 } else if (args.length >= 1 && args[0].equals(0)) { 2065 audioOff(); 2066 return new AtCommandResult(AtCommandResult.OK); 2067 } 2068 return new AtCommandResult(AtCommandResult.ERROR); 2069 } 2070 @Override 2071 public AtCommandResult handleTestCommand() { 2072 return new AtCommandResult("+BVRA: (0-1)"); 2073 } 2074 }); 2075 2076 // Retrieve Subscriber Number 2077 parser.register("+CNUM", new AtCommandHandler() { 2078 @Override 2079 public AtCommandResult handleActionCommand() { 2080 String number = mPhone.getLine1Number(); 2081 if (number == null) { 2082 return new AtCommandResult(AtCommandResult.OK); 2083 } 2084 return new AtCommandResult("+CNUM: ,\"" + number + "\"," + 2085 PhoneNumberUtils.toaFromString(number) + ",,4"); 2086 } 2087 }); 2088 2089 // Microphone Gain 2090 parser.register("+VGM", new AtCommandHandler() { 2091 @Override 2092 public AtCommandResult handleSetCommand(Object[] args) { 2093 // AT+VGM=<gain> in range [0,15] 2094 // Headset/Handsfree is reporting its current gain setting 2095 return new AtCommandResult(AtCommandResult.OK); 2096 } 2097 }); 2098 2099 // Speaker Gain 2100 parser.register("+VGS", new AtCommandHandler() { 2101 @Override 2102 public AtCommandResult handleSetCommand(Object[] args) { 2103 // AT+VGS=<gain> in range [0,15] 2104 if (args.length != 1 || !(args[0] instanceof Integer)) { 2105 return new AtCommandResult(AtCommandResult.ERROR); 2106 } 2107 mScoGain = (Integer) args[0]; 2108 int flag = mAudioManager.isBluetoothScoOn() ? AudioManager.FLAG_SHOW_UI:0; 2109 2110 mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, mScoGain, flag); 2111 return new AtCommandResult(AtCommandResult.OK); 2112 } 2113 }); 2114 2115 // Phone activity status 2116 parser.register("+CPAS", new AtCommandHandler() { 2117 @Override 2118 public AtCommandResult handleActionCommand() { 2119 int status = 0; 2120 switch (mPhone.getState()) { 2121 case IDLE: 2122 status = 0; 2123 break; 2124 case RINGING: 2125 status = 3; 2126 break; 2127 case OFFHOOK: 2128 status = 4; 2129 break; 2130 } 2131 return new AtCommandResult("+CPAS: " + status); 2132 } 2133 }); 2134 mPhonebook.register(parser); 2135 } 2136 2137 public void sendScoGainUpdate(int gain) { 2138 if (mScoGain != gain && (mRemoteBrsf & BRSF_HF_REMOTE_VOL_CONTROL) != 0x0) { 2139 sendURC("+VGS:" + gain); 2140 mScoGain = gain; 2141 } 2142 } 2143 2144 public AtCommandResult reportCmeError(int error) { 2145 if (mCmee) { 2146 AtCommandResult result = new AtCommandResult(AtCommandResult.UNSOLICITED); 2147 result.addResponse("+CME ERROR: " + error); 2148 return result; 2149 } else { 2150 return new AtCommandResult(AtCommandResult.ERROR); 2151 } 2152 } 2153 2154 private static final int START_CALL_TIMEOUT = 10000; // ms 2155 2156 private synchronized void expectCallStart() { 2157 mWaitingForCallStart = true; 2158 Message msg = Message.obtain(mHandler, CHECK_CALL_STARTED); 2159 mHandler.sendMessageDelayed(msg, START_CALL_TIMEOUT); 2160 if (!mStartCallWakeLock.isHeld()) { 2161 mStartCallWakeLock.acquire(START_CALL_TIMEOUT); 2162 } 2163 } 2164 2165 private synchronized void callStarted() { 2166 if (mWaitingForCallStart) { 2167 mWaitingForCallStart = false; 2168 sendURC("OK"); 2169 if (mStartCallWakeLock.isHeld()) { 2170 mStartCallWakeLock.release(); 2171 } 2172 } 2173 } 2174 2175 private static final int START_VOICE_RECOGNITION_TIMEOUT = 5000; // ms 2176 2177 private synchronized void expectVoiceRecognition() { 2178 mWaitingForVoiceRecognition = true; 2179 Message msg = Message.obtain(mHandler, CHECK_VOICE_RECOGNITION_STARTED); 2180 mHandler.sendMessageDelayed(msg, START_VOICE_RECOGNITION_TIMEOUT); 2181 if (!mStartVoiceRecognitionWakeLock.isHeld()) { 2182 mStartVoiceRecognitionWakeLock.acquire(START_VOICE_RECOGNITION_TIMEOUT); 2183 } 2184 } 2185 2186 /* package */ synchronized boolean startVoiceRecognition() { 2187 if (mWaitingForVoiceRecognition) { 2188 // HF initiated 2189 mWaitingForVoiceRecognition = false; 2190 sendURC("OK"); 2191 } else { 2192 // AG initiated 2193 sendURC("+BVRA: 1"); 2194 } 2195 boolean ret = audioOn(); 2196 if (mStartVoiceRecognitionWakeLock.isHeld()) { 2197 mStartVoiceRecognitionWakeLock.release(); 2198 } 2199 return ret; 2200 } 2201 2202 /* package */ synchronized boolean stopVoiceRecognition() { 2203 sendURC("+BVRA: 0"); 2204 audioOff(); 2205 return true; 2206 } 2207 2208 private boolean inDebug() { 2209 return DBG && SystemProperties.getBoolean(DebugThread.DEBUG_HANDSFREE, false); 2210 } 2211 2212 private boolean allowAudioAnytime() { 2213 return inDebug() && SystemProperties.getBoolean(DebugThread.DEBUG_HANDSFREE_AUDIO_ANYTIME, 2214 false); 2215 } 2216 2217 private void startDebug() { 2218 if (DBG && mDebugThread == null) { 2219 mDebugThread = new DebugThread(); 2220 mDebugThread.start(); 2221 } 2222 } 2223 2224 private void stopDebug() { 2225 if (mDebugThread != null) { 2226 mDebugThread.interrupt(); 2227 mDebugThread = null; 2228 } 2229 } 2230 2231 /** Debug thread to read debug properties - runs when debug.bt.hfp is true 2232 * at the time a bluetooth handsfree device is connected. Debug properties 2233 * are polled and mock updates sent every 1 second */ 2234 private class DebugThread extends Thread { 2235 /** Turns on/off handsfree profile debugging mode */ 2236 private static final String DEBUG_HANDSFREE = "debug.bt.hfp"; 2237 2238 /** Mock battery level change - use 0 to 5 */ 2239 private static final String DEBUG_HANDSFREE_BATTERY = "debug.bt.hfp.battery"; 2240 2241 /** Mock no cellular service when false */ 2242 private static final String DEBUG_HANDSFREE_SERVICE = "debug.bt.hfp.service"; 2243 2244 /** Mock cellular roaming when true */ 2245 private static final String DEBUG_HANDSFREE_ROAM = "debug.bt.hfp.roam"; 2246 2247 /** false to true transition will force an audio (SCO) connection to 2248 * be established. true to false will force audio to be disconnected 2249 */ 2250 private static final String DEBUG_HANDSFREE_AUDIO = "debug.bt.hfp.audio"; 2251 2252 /** true allows incoming SCO connection out of call. 2253 */ 2254 private static final String DEBUG_HANDSFREE_AUDIO_ANYTIME = "debug.bt.hfp.audio_anytime"; 2255 2256 /** Mock signal strength change in ASU - use 0 to 31 */ 2257 private static final String DEBUG_HANDSFREE_SIGNAL = "debug.bt.hfp.signal"; 2258 2259 /** Debug AT+CLCC: print +CLCC result */ 2260 private static final String DEBUG_HANDSFREE_CLCC = "debug.bt.hfp.clcc"; 2261 2262 /** Debug AT+BSIR - Send In Band Ringtones Unsolicited AT command. 2263 * debug.bt.unsol.inband = 0 => AT+BSIR = 0 sent by the AG 2264 * debug.bt.unsol.inband = 1 => AT+BSIR = 0 sent by the AG 2265 * Other values are ignored. 2266 */ 2267 2268 private static final String DEBUG_UNSOL_INBAND_RINGTONE = 2269 "debug.bt.unsol.inband"; 2270 2271 @Override 2272 public void run() { 2273 boolean oldService = true; 2274 boolean oldRoam = false; 2275 boolean oldAudio = false; 2276 2277 while (!isInterrupted() && inDebug()) { 2278 int batteryLevel = SystemProperties.getInt(DEBUG_HANDSFREE_BATTERY, -1); 2279 if (batteryLevel >= 0 && batteryLevel <= 5) { 2280 Intent intent = new Intent(); 2281 intent.putExtra("level", batteryLevel); 2282 intent.putExtra("scale", 5); 2283 mBluetoothPhoneState.updateBatteryState(intent); 2284 } 2285 2286 boolean serviceStateChanged = false; 2287 if (SystemProperties.getBoolean(DEBUG_HANDSFREE_SERVICE, true) != oldService) { 2288 oldService = !oldService; 2289 serviceStateChanged = true; 2290 } 2291 if (SystemProperties.getBoolean(DEBUG_HANDSFREE_ROAM, false) != oldRoam) { 2292 oldRoam = !oldRoam; 2293 serviceStateChanged = true; 2294 } 2295 if (serviceStateChanged) { 2296 Bundle b = new Bundle(); 2297 b.putInt("state", oldService ? 0 : 1); 2298 b.putBoolean("roaming", oldRoam); 2299 mBluetoothPhoneState.updateServiceState(true, ServiceState.newFromBundle(b)); 2300 } 2301 2302 if (SystemProperties.getBoolean(DEBUG_HANDSFREE_AUDIO, false) != oldAudio) { 2303 oldAudio = !oldAudio; 2304 if (oldAudio) { 2305 audioOn(); 2306 } else { 2307 audioOff(); 2308 } 2309 } 2310 2311 int signalLevel = SystemProperties.getInt(DEBUG_HANDSFREE_SIGNAL, -1); 2312 if (signalLevel >= 0 && signalLevel <= 31) { 2313 SignalStrength signalStrength = new SignalStrength(signalLevel, -1, -1, -1, 2314 -1, -1, -1, true); 2315 Intent intent = new Intent(); 2316 Bundle data = new Bundle(); 2317 signalStrength.fillInNotifierBundle(data); 2318 intent.putExtras(data); 2319 mBluetoothPhoneState.updateSignalState(intent); 2320 } 2321 2322 if (SystemProperties.getBoolean(DEBUG_HANDSFREE_CLCC, false)) { 2323 log(gsmGetClccResult().toString()); 2324 } 2325 try { 2326 sleep(1000); // 1 second 2327 } catch (InterruptedException e) { 2328 break; 2329 } 2330 2331 int inBandRing = 2332 SystemProperties.getInt(DEBUG_UNSOL_INBAND_RINGTONE, -1); 2333 if (inBandRing == 0 || inBandRing == 1) { 2334 AtCommandResult result = 2335 new AtCommandResult(AtCommandResult.UNSOLICITED); 2336 result.addResponse("+BSIR: " + inBandRing); 2337 sendURC(result.toString()); 2338 } 2339 } 2340 } 2341 } 2342 2343 public void cdmaSwapSecondCallState() { 2344 if (VDBG) log("cdmaSetSecondCallState: Toggling mCdmaIsSecondCallActive"); 2345 mCdmaIsSecondCallActive = !mCdmaIsSecondCallActive; 2346 } 2347 2348 public void cdmaSetSecondCallState(boolean state) { 2349 if (VDBG) log("cdmaSetSecondCallState: Setting mCdmaIsSecondCallActive to " + state); 2350 mCdmaIsSecondCallActive = state; 2351 } 2352 2353 private static void log(String msg) { 2354 Log.d(TAG, msg); 2355 } 2356 } 2357