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