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