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