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