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