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