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