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