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