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