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