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