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