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