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