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