HeadsetStateMachine.java revision 579f67ec87646f840c1235eb62d99ab9fa11f23c
1/* 2 * Copyright (C) 2012 Google Inc. 3 */ 4 5/** 6 * Bluetooth Handset StateMachine 7 * (Disconnected) 8 * | ^ 9 * CONNECT | | DISCONNECTED 10 * V | 11 * (Pending) 12 * | ^ 13 * CONNECTED | | CONNECT 14 * V | 15 * (Connected) 16 * | ^ 17 * CONNECT_AUDIO | | DISCONNECT_AUDIO 18 * V | 19 * (AudioOn) 20 */ 21package com.android.bluetooth.hfp; 22 23import android.bluetooth.BluetoothAdapter; 24import android.bluetooth.BluetoothDevice; 25import android.bluetooth.BluetoothHeadset; 26import android.bluetooth.BluetoothProfile; 27import android.bluetooth.BluetoothUuid; 28import android.bluetooth.IBluetooth; 29import android.bluetooth.IBluetoothHeadsetPhone; 30import android.content.ComponentName; 31import android.content.Context; 32import android.content.Intent; 33import android.content.ServiceConnection; 34import android.content.ActivityNotFoundException; 35import android.media.AudioManager; 36import android.net.Uri; 37import android.os.IBinder; 38import android.os.Message; 39import android.os.ParcelUuid; 40import android.os.RemoteException; 41import android.os.ServiceManager; 42import android.os.PowerManager; 43import android.os.PowerManager.WakeLock; 44import android.telephony.PhoneNumberUtils; 45import android.util.Log; 46import com.android.bluetooth.Utils; 47import com.android.bluetooth.btservice.AdapterService; 48import com.android.internal.util.IState; 49import com.android.internal.util.State; 50import com.android.internal.util.StateMachine; 51import java.util.ArrayList; 52import java.util.List; 53import java.util.Set; 54 55final class HeadsetStateMachine extends StateMachine { 56 private static final String TAG = "HeadsetStateMachine"; 57 private static final boolean DBG = true; 58 //For Debugging only 59 private static int sRefCount=0; 60 61 private static final String HEADSET_NAME = "bt_headset_name"; 62 private static final String HEADSET_NREC = "bt_headset_nrec"; 63 64 static final int CONNECT = 1; 65 static final int DISCONNECT = 2; 66 static final int CONNECT_AUDIO = 3; 67 static final int DISCONNECT_AUDIO = 4; 68 static final int VOICE_RECOGNITION_START = 5; 69 static final int VOICE_RECOGNITION_STOP = 6; 70 71 // message.obj is an intent AudioManager.VOLUME_CHANGED_ACTION 72 // EXTRA_VOLUME_STREAM_TYPE is STREAM_BLUETOOTH_SCO 73 static final int INTENT_SCO_VOLUME_CHANGED = 7; 74 static final int SET_MIC_VOLUME = 8; 75 static final int CALL_STATE_CHANGED = 9; 76 static final int INTENT_BATTERY_CHANGED = 10; 77 static final int DEVICE_STATE_CHANGED = 11; 78 static final int ROAM_CHANGED = 12; 79 static final int SEND_CCLC_RESPONSE = 13; 80 81 private static final int STACK_EVENT = 101; 82 private static final int DIALING_OUT_TIMEOUT = 102; 83 private static final int START_VR_TIMEOUT = 103; 84 85 private static final int CONNECT_TIMEOUT = 201; 86 87 private static final int DIALING_OUT_TIMEOUT_VALUE = 10000; 88 private static final int START_VR_TIMEOUT_VALUE = 5000; 89 90 private static final ParcelUuid[] HEADSET_UUIDS = { 91 BluetoothUuid.HSP, 92 BluetoothUuid.Handsfree, 93 }; 94 95 private Disconnected mDisconnected; 96 private Pending mPending; 97 private Connected mConnected; 98 private AudioOn mAudioOn; 99 100 private HeadsetService mService; 101 private PowerManager mPowerManager; 102 private boolean mVoiceRecognitionStarted = false; 103 private boolean mWaitingForVoiceRecognition = false; 104 private WakeLock mStartVoiceRecognitionWakeLock; // held while waiting for voice recognition 105 106 private boolean mDialingOut = false; 107 private AudioManager mAudioManager; 108 private AtPhonebook mPhonebook; 109 110 private static Intent sVoiceCommandIntent; 111 112 private HeadsetPhoneState mPhoneState; 113 private int mAudioState; 114 private BluetoothAdapter mAdapter; 115 private IBluetoothHeadsetPhone mPhoneProxy; 116 private boolean mNativeAvailable; 117 118 // mCurrentDevice is the device connected before the state changes 119 // mTargetDevice is the device to be connected 120 // mIncomingDevice is the device connecting to us, valid only in Pending state 121 // when mIncomingDevice is not null, both mCurrentDevice 122 // and mTargetDevice are null 123 // when either mCurrentDevice or mTargetDevice is not null, 124 // mIncomingDevice is null 125 // Stable states 126 // No connection, Disconnected state 127 // both mCurrentDevice and mTargetDevice are null 128 // Connected, Connected state 129 // mCurrentDevice is not null, mTargetDevice is null 130 // Interim states 131 // Connecting to a device, Pending 132 // mCurrentDevice is null, mTargetDevice is not null 133 // Disconnecting device, Connecting to new device 134 // Pending 135 // Both mCurrentDevice and mTargetDevice are not null 136 // Disconnecting device Pending 137 // mCurrentDevice is not null, mTargetDevice is null 138 // Incoming connections Pending 139 // Both mCurrentDevice and mTargetDevice are null 140 private BluetoothDevice mCurrentDevice = null; 141 private BluetoothDevice mTargetDevice = null; 142 private BluetoothDevice mIncomingDevice = null; 143 144 static { 145 classInitNative(); 146 } 147 148 HeadsetStateMachine(HeadsetService context) { 149 super(TAG); 150 mService = context; 151 mVoiceRecognitionStarted = false; 152 mWaitingForVoiceRecognition = false; 153 154 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 155 mStartVoiceRecognitionWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 156 TAG + ":VoiceRecognition"); 157 mStartVoiceRecognitionWakeLock.setReferenceCounted(false); 158 159 mDialingOut = false; 160 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 161 mPhonebook = new AtPhonebook(mService, this); 162 mPhoneState = new HeadsetPhoneState(context, this); 163 mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 164 mAdapter = BluetoothAdapter.getDefaultAdapter(); 165 if (!context.bindService(new Intent(IBluetoothHeadsetPhone.class.getName()), 166 mConnection, 0)) { 167 Log.e(TAG, "Could not bind to Bluetooth Headset Phone Service"); 168 } 169 170 initializeNative(); 171 mNativeAvailable=true; 172 173 mDisconnected = new Disconnected(); 174 mPending = new Pending(); 175 mConnected = new Connected(); 176 mAudioOn = new AudioOn(); 177 178 if (sVoiceCommandIntent == null) { 179 sVoiceCommandIntent = new Intent(Intent.ACTION_VOICE_COMMAND); 180 sVoiceCommandIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 181 } 182 183 addState(mDisconnected); 184 addState(mPending); 185 addState(mConnected); 186 addState(mAudioOn); 187 188 setInitialState(mDisconnected); 189 } 190 191 public void cleanup() { 192 if (mPhoneProxy != null) { 193 if (DBG) Log.d(TAG,"Unbinding service..."); 194 synchronized (mConnection) { 195 try { 196 mPhoneProxy = null; 197 mService.unbindService(mConnection); 198 } catch (Exception re) { 199 Log.e(TAG,"Error unbinding from IBluetoothHeadsetPhone",re); 200 } 201 } 202 } 203 if (mPhoneState != null) { 204 mPhoneState.listenForPhoneState(false); 205 mPhoneState.cleanup(); 206 mPhoneState=null; 207 } 208 if (mPhonebook != null) { 209 mPhonebook.cleanup(); 210 mPhonebook = null; 211 } 212 if (mNativeAvailable) { 213 cleanupNative(); 214 mNativeAvailable = false; 215 } 216 mService = null; 217 mAdapter = null; 218 } 219 220 private class Disconnected extends State { 221 @Override 222 public void enter() { 223 log("Enter Disconnected: " + getCurrentMessage().what); 224 mPhonebook.resetAtState(); 225 mPhoneState.listenForPhoneState(false); 226 } 227 228 @Override 229 public boolean processMessage(Message message) { 230 log("Disconnected process message: " + message.what); 231 if (DBG) { 232 if (mCurrentDevice != null || mTargetDevice != null || mIncomingDevice != null) { 233 log("ERROR: current, target, or mIncomingDevice not null in Disconnected"); 234 return NOT_HANDLED; 235 } 236 } 237 238 boolean retValue = HANDLED; 239 switch(message.what) { 240 case CONNECT: 241 BluetoothDevice device = (BluetoothDevice) message.obj; 242 broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING, 243 BluetoothProfile.STATE_DISCONNECTED); 244 245 if (!connectHfpNative(getByteAddress(device)) ) { 246 broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED, 247 BluetoothProfile.STATE_CONNECTING); 248 break; 249 } 250 251 synchronized (HeadsetStateMachine.this) { 252 mTargetDevice = device; 253 transitionTo(mPending); 254 } 255 // TODO(BT) remove CONNECT_TIMEOUT when the stack 256 // sends back events consistently 257 sendMessageDelayed(CONNECT_TIMEOUT, 30000); 258 break; 259 case DISCONNECT: 260 // ignore 261 break; 262 case INTENT_BATTERY_CHANGED: 263 processIntentBatteryChanged((Intent) message.obj); 264 break; 265 case ROAM_CHANGED: 266 processRoamChanged((Boolean) message.obj); 267 break; 268 case CALL_STATE_CHANGED: 269 processCallState((HeadsetCallState) message.obj); 270 break; 271 case STACK_EVENT: 272 StackEvent event = (StackEvent) message.obj; 273 if (DBG) { 274 log("event type: " + event.type); 275 } 276 switch (event.type) { 277 case EVENT_TYPE_CONNECTION_STATE_CHANGED: 278 processConnectionEvent(event.valueInt, event.device); 279 break; 280 default: 281 Log.e(TAG, "Unexpected stack event: " + event.type); 282 break; 283 } 284 break; 285 default: 286 return NOT_HANDLED; 287 } 288 return retValue; 289 } 290 291 @Override 292 public void exit() { 293 log("Exit Disconnected: " + getCurrentMessage().what); 294 mPhoneState.listenForPhoneState(true); 295 } 296 297 // in Disconnected state 298 private void processConnectionEvent(int state, BluetoothDevice device) { 299 switch (state) { 300 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED: 301 Log.w(TAG, "Ignore HF DISCONNECTED event, device: " + device); 302 break; 303 case HeadsetHalConstants.CONNECTION_STATE_CONNECTING: 304 // check priority and accept or reject the connection 305 // Since the state changes to Connecting or directly Connected in some cases.Have the check both in 306 // CONNECTION_STATE_CONNECTING and CONNECTION_STATE_CONNECTED. 307 if (BluetoothProfile.PRIORITY_OFF < mService.getPriority(device)) { 308 Log.i(TAG,"Incoming Hf accepted"); 309 // TODO(BT) Assume it's incoming connection 310 // Do we need to check priority and accept/reject accordingly? 311 broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING, 312 BluetoothProfile.STATE_DISCONNECTED); 313 synchronized (HeadsetStateMachine.this) { 314 mIncomingDevice = device; 315 transitionTo(mPending); 316 } 317 } else { 318 Log.i(TAG,"Incoming Hf rejected"); 319 //reject the connection and stay in Disconnected state itself 320 disconnectHfpNative(getByteAddress(device)); 321 } 322 break; 323 case HeadsetHalConstants.CONNECTION_STATE_CONNECTED: 324 Log.w(TAG, "HFP Connected from Disconnected state"); 325 if (BluetoothProfile.PRIORITY_OFF < mService.getPriority(device)) { 326 Log.i(TAG,"Incoming Hf accepted"); 327 broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED, 328 BluetoothProfile.STATE_DISCONNECTED); 329 synchronized (HeadsetStateMachine.this) { 330 mCurrentDevice = device; 331 transitionTo(mConnected); 332 } 333 configAudioParameters(); 334 } else { 335 //reject the connection and stay in Disconnected state itself 336 Log.d(TAG,"Incoming Hf rejected"); 337 disconnectHfpNative(getByteAddress(device)); 338 } 339 340 break; 341 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING: 342 Log.w(TAG, "Ignore HF DISCONNECTING event, device: " + device); 343 break; 344 default: 345 Log.e(TAG, "Incorrect state: " + state); 346 break; 347 } 348 } 349 } 350 351 private class Pending extends State { 352 @Override 353 public void enter() { 354 log("Enter Pending: " + getCurrentMessage().what); 355 } 356 357 @Override 358 public boolean processMessage(Message message) { 359 log("Pending process message: " + message.what); 360 361 boolean retValue = HANDLED; 362 switch(message.what) { 363 case CONNECT: 364 case CONNECT_AUDIO: 365 deferMessage(message); 366 break; 367 case CONNECT_TIMEOUT: 368 onConnectionStateChanged(HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED, 369 getByteAddress(mTargetDevice)); 370 break; 371 case DISCONNECT: 372 BluetoothDevice device = (BluetoothDevice) message.obj; 373 if (mCurrentDevice != null && mTargetDevice != null && 374 mTargetDevice.equals(device) ) { 375 // cancel connection to the mTargetDevice 376 broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED, 377 BluetoothProfile.STATE_CONNECTING); 378 synchronized (HeadsetStateMachine.this) { 379 mTargetDevice = null; 380 } 381 } else { 382 deferMessage(message); 383 } 384 break; 385 case INTENT_BATTERY_CHANGED: 386 processIntentBatteryChanged((Intent) message.obj); 387 break; 388 case ROAM_CHANGED: 389 processRoamChanged((Boolean) message.obj); 390 break; 391 case CALL_STATE_CHANGED: 392 processCallState((HeadsetCallState) message.obj); 393 break; 394 case STACK_EVENT: 395 StackEvent event = (StackEvent) message.obj; 396 if (DBG) { 397 log("event type: " + event.type); 398 } 399 switch (event.type) { 400 case EVENT_TYPE_CONNECTION_STATE_CHANGED: 401 removeMessages(CONNECT_TIMEOUT); 402 processConnectionEvent(event.valueInt, event.device); 403 break; 404 default: 405 Log.e(TAG, "Unexpected event: " + event.type); 406 break; 407 } 408 break; 409 default: 410 return NOT_HANDLED; 411 } 412 return retValue; 413 } 414 415 // in Pending state 416 private void processConnectionEvent(int state, BluetoothDevice device) { 417 switch (state) { 418 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED: 419 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) { 420 broadcastConnectionState(mCurrentDevice, 421 BluetoothProfile.STATE_DISCONNECTED, 422 BluetoothProfile.STATE_DISCONNECTING); 423 synchronized (HeadsetStateMachine.this) { 424 mCurrentDevice = null; 425 } 426 427 if (mTargetDevice != null) { 428 if (!connectHfpNative(getByteAddress(mTargetDevice))) { 429 broadcastConnectionState(mTargetDevice, 430 BluetoothProfile.STATE_DISCONNECTED, 431 BluetoothProfile.STATE_CONNECTING); 432 synchronized (HeadsetStateMachine.this) { 433 mTargetDevice = null; 434 transitionTo(mDisconnected); 435 } 436 } 437 } else { 438 synchronized (HeadsetStateMachine.this) { 439 mIncomingDevice = null; 440 transitionTo(mDisconnected); 441 } 442 } 443 } else if (mTargetDevice != null && mTargetDevice.equals(device)) { 444 // outgoing connection failed 445 broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED, 446 BluetoothProfile.STATE_CONNECTING); 447 synchronized (HeadsetStateMachine.this) { 448 mTargetDevice = null; 449 transitionTo(mDisconnected); 450 } 451 } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) { 452 broadcastConnectionState(mIncomingDevice, 453 BluetoothProfile.STATE_DISCONNECTED, 454 BluetoothProfile.STATE_CONNECTING); 455 synchronized (HeadsetStateMachine.this) { 456 mIncomingDevice = null; 457 transitionTo(mDisconnected); 458 } 459 } else { 460 Log.e(TAG, "Unknown device Disconnected: " + device); 461 } 462 break; 463 case HeadsetHalConstants.CONNECTION_STATE_CONNECTED: 464 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) { 465 // disconnection failed 466 broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED, 467 BluetoothProfile.STATE_DISCONNECTING); 468 if (mTargetDevice != null) { 469 broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED, 470 BluetoothProfile.STATE_CONNECTING); 471 } 472 synchronized (HeadsetStateMachine.this) { 473 mTargetDevice = null; 474 transitionTo(mConnected); 475 } 476 } else if (mTargetDevice != null && mTargetDevice.equals(device)) { 477 broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED, 478 BluetoothProfile.STATE_CONNECTING); 479 synchronized (HeadsetStateMachine.this) { 480 mCurrentDevice = mTargetDevice; 481 mTargetDevice = null; 482 transitionTo(mConnected); 483 } 484 } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) { 485 broadcastConnectionState(mIncomingDevice, BluetoothProfile.STATE_CONNECTED, 486 BluetoothProfile.STATE_CONNECTING); 487 synchronized (HeadsetStateMachine.this) { 488 mCurrentDevice = mIncomingDevice; 489 mIncomingDevice = null; 490 transitionTo(mConnected); 491 } 492 } else { 493 Log.e(TAG, "Unknown device Connected: " + device); 494 // something is wrong here, but sync our state with stack 495 broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED, 496 BluetoothProfile.STATE_DISCONNECTED); 497 synchronized (HeadsetStateMachine.this) { 498 mCurrentDevice = device; 499 mTargetDevice = null; 500 mIncomingDevice = null; 501 transitionTo(mConnected); 502 } 503 } 504 configAudioParameters(); 505 break; 506 case HeadsetHalConstants.CONNECTION_STATE_CONNECTING: 507 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) { 508 log("current device tries to connect back"); 509 // TODO(BT) ignore or reject 510 } else if (mTargetDevice != null && mTargetDevice.equals(device)) { 511 // The stack is connecting to target device or 512 // there is an incoming connection from the target device at the same time 513 // we already broadcasted the intent, doing nothing here 514 if (DBG) { 515 log("Stack and target device are connecting"); 516 } 517 } 518 else if (mIncomingDevice != null && mIncomingDevice.equals(device)) { 519 Log.e(TAG, "Another connecting event on the incoming device"); 520 } else { 521 // We get an incoming connecting request while Pending 522 // TODO(BT) is stack handing this case? let's ignore it for now 523 log("Incoming connection while pending, ignore"); 524 } 525 break; 526 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING: 527 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) { 528 // we already broadcasted the intent, doing nothing here 529 if (DBG) { 530 log("stack is disconnecting mCurrentDevice"); 531 } 532 } else if (mTargetDevice != null && mTargetDevice.equals(device)) { 533 Log.e(TAG, "TargetDevice is getting disconnected"); 534 } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) { 535 Log.e(TAG, "IncomingDevice is getting disconnected"); 536 } else { 537 Log.e(TAG, "Disconnecting unknow device: " + device); 538 } 539 break; 540 default: 541 Log.e(TAG, "Incorrect state: " + state); 542 break; 543 } 544 } 545 546 } 547 548 private class Connected extends State { 549 @Override 550 public void enter() { 551 log("Enter Connected: " + getCurrentMessage().what); 552 } 553 554 @Override 555 public boolean processMessage(Message message) { 556 log("Connected process message: " + message.what); 557 if (DBG) { 558 if (mCurrentDevice == null) { 559 log("ERROR: mCurrentDevice is null in Connected"); 560 return NOT_HANDLED; 561 } 562 } 563 564 boolean retValue = HANDLED; 565 switch(message.what) { 566 case CONNECT: 567 { 568 BluetoothDevice device = (BluetoothDevice) message.obj; 569 if (mCurrentDevice.equals(device)) { 570 break; 571 } 572 573 broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING, 574 BluetoothProfile.STATE_DISCONNECTED); 575 if (!disconnectHfpNative(getByteAddress(mCurrentDevice))) { 576 broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED, 577 BluetoothProfile.STATE_CONNECTING); 578 break; 579 } 580 581 synchronized (HeadsetStateMachine.this) { 582 mTargetDevice = device; 583 transitionTo(mPending); 584 } 585 } 586 break; 587 case DISCONNECT: 588 { 589 BluetoothDevice device = (BluetoothDevice) message.obj; 590 if (!mCurrentDevice.equals(device)) { 591 break; 592 } 593 broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING, 594 BluetoothProfile.STATE_CONNECTED); 595 if (!disconnectHfpNative(getByteAddress(device))) { 596 broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED, 597 BluetoothProfile.STATE_DISCONNECTED); 598 break; 599 } 600 transitionTo(mPending); 601 } 602 break; 603 case CONNECT_AUDIO: 604 // TODO(BT) when failure, broadcast audio connecting to disconnected intent 605 // check if device matches mCurrentDevice 606 connectAudioNative(getByteAddress(mCurrentDevice)); 607 break; 608 case VOICE_RECOGNITION_START: 609 processLocalVrEvent(HeadsetHalConstants.VR_STATE_STARTED); 610 break; 611 case VOICE_RECOGNITION_STOP: 612 processLocalVrEvent(HeadsetHalConstants.VR_STATE_STOPPED); 613 break; 614 case CALL_STATE_CHANGED: 615 processCallState((HeadsetCallState) message.obj); 616 break; 617 case INTENT_BATTERY_CHANGED: 618 processIntentBatteryChanged((Intent) message.obj); 619 break; 620 case ROAM_CHANGED: 621 processRoamChanged((Boolean) message.obj); 622 break; 623 case DEVICE_STATE_CHANGED: 624 processDeviceStateChanged((HeadsetDeviceState) message.obj); 625 break; 626 case SEND_CCLC_RESPONSE: 627 processSendClccResponse((HeadsetClccResponse) message.obj); 628 break; 629 case DIALING_OUT_TIMEOUT: 630 if (mDialingOut) { 631 mDialingOut= false; 632 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 633 } 634 break; 635 case START_VR_TIMEOUT: 636 if (mWaitingForVoiceRecognition) { 637 mWaitingForVoiceRecognition = false; 638 Log.e(TAG, "Timeout waiting for voice recognition to start"); 639 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 640 } 641 break; 642 case STACK_EVENT: 643 StackEvent event = (StackEvent) message.obj; 644 if (DBG) { 645 log("event type: " + event.type); 646 } 647 switch (event.type) { 648 case EVENT_TYPE_CONNECTION_STATE_CHANGED: 649 processConnectionEvent(event.valueInt, event.device); 650 break; 651 case EVENT_TYPE_AUDIO_STATE_CHANGED: 652 processAudioEvent(event.valueInt, event.device); 653 break; 654 case EVENT_TYPE_VR_STATE_CHANGED: 655 processVrEvent(event.valueInt); 656 break; 657 case EVENT_TYPE_ANSWER_CALL: 658 // TODO(BT) could answer call happen on Connected state? 659 processAnswerCall(); 660 break; 661 case EVENT_TYPE_HANGUP_CALL: 662 // TODO(BT) could hangup call happen on Connected state? 663 processHangupCall(); 664 break; 665 case EVENT_TYPE_VOLUME_CHANGED: 666 processVolumeEvent(event.valueInt, event.valueInt2); 667 break; 668 case EVENT_TYPE_DIAL_CALL: 669 processDialCall(event.valueString); 670 break; 671 case EVENT_TYPE_SEND_DTMF: 672 processSendDtmf(event.valueInt); 673 break; 674 case EVENT_TYPE_NOICE_REDUCTION: 675 processNoiceReductionEvent(event.valueInt); 676 break; 677 case EVENT_TYPE_AT_CHLD: 678 processAtChld(event.valueInt); 679 break; 680 case EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST: 681 processSubscriberNumberRequest(); 682 break; 683 case EVENT_TYPE_AT_CIND: 684 processAtCind(); 685 break; 686 case EVENT_TYPE_AT_COPS: 687 processAtCops(); 688 break; 689 case EVENT_TYPE_AT_CLCC: 690 processAtClcc(); 691 break; 692 case EVENT_TYPE_UNKNOWN_AT: 693 processUnknownAt(event.valueString); 694 break; 695 case EVENT_TYPE_KEY_PRESSED: 696 processKeyPressed(); 697 break; 698 default: 699 Log.e(TAG, "Unknown stack event: " + event.type); 700 break; 701 } 702 break; 703 default: 704 return NOT_HANDLED; 705 } 706 return retValue; 707 } 708 709 // in Connected state 710 private void processConnectionEvent(int state, BluetoothDevice device) { 711 switch (state) { 712 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED: 713 if (mCurrentDevice.equals(device)) { 714 broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED, 715 BluetoothProfile.STATE_CONNECTED); 716 synchronized (HeadsetStateMachine.this) { 717 mCurrentDevice = null; 718 transitionTo(mDisconnected); 719 } 720 } else { 721 Log.e(TAG, "Disconnected from unknown device: " + device); 722 } 723 break; 724 case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED: 725 processSlcConnected(); 726 break; 727 default: 728 Log.e(TAG, "Connection State Device: " + device + " bad state: " + state); 729 break; 730 } 731 } 732 733 // in Connected state 734 private void processAudioEvent(int state, BluetoothDevice device) { 735 if (!mCurrentDevice.equals(device)) { 736 Log.e(TAG, "Audio changed on disconnected device: " + device); 737 return; 738 } 739 740 switch (state) { 741 case HeadsetHalConstants.AUDIO_STATE_CONNECTED: 742 // TODO(BT) should I save the state for next broadcast as the prevState? 743 mAudioState = BluetoothHeadset.STATE_AUDIO_CONNECTED; 744 mAudioManager.setBluetoothScoOn(true); 745 broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_CONNECTED, 746 BluetoothHeadset.STATE_AUDIO_CONNECTING); 747 transitionTo(mAudioOn); 748 break; 749 case HeadsetHalConstants.AUDIO_STATE_CONNECTING: 750 mAudioState = BluetoothHeadset.STATE_AUDIO_CONNECTING; 751 broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_CONNECTING, 752 BluetoothHeadset.STATE_AUDIO_DISCONNECTED); 753 break; 754 // TODO(BT) process other states 755 default: 756 Log.e(TAG, "Audio State Device: " + device + " bad state: " + state); 757 break; 758 } 759 } 760 761 private void processSlcConnected() { 762 if (mPhoneProxy != null) { 763 try { 764 mPhoneProxy.queryPhoneState(); 765 } catch (RemoteException e) { 766 Log.e(TAG, Log.getStackTraceString(new Throwable())); 767 } 768 } else { 769 Log.e(TAG, "Handsfree phone proxy null for query phone state"); 770 } 771 772 } 773 } 774 775 private class AudioOn extends State { 776 777 @Override 778 public void enter() { 779 log("Enter AudioOn: " + getCurrentMessage().what); 780 } 781 782 @Override 783 public boolean processMessage(Message message) { 784 log("AudioOn process message: " + message.what); 785 if (DBG) { 786 if (mCurrentDevice == null) { 787 log("ERROR: mCurrentDevice is null in AudioOn"); 788 return NOT_HANDLED; 789 } 790 } 791 792 boolean retValue = HANDLED; 793 switch(message.what) { 794 case DISCONNECT: 795 { 796 BluetoothDevice device = (BluetoothDevice) message.obj; 797 if (!mCurrentDevice.equals(device)) { 798 break; 799 } 800 deferMessage(obtainMessage(DISCONNECT, message.obj)); 801 } 802 // fall through 803 case DISCONNECT_AUDIO: 804 if (disconnectAudioNative(getByteAddress(mCurrentDevice))) { 805 mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 806 mAudioManager.setBluetoothScoOn(false); 807 broadcastAudioState(mCurrentDevice, BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 808 BluetoothHeadset.STATE_AUDIO_CONNECTED); 809 } 810 break; 811 case VOICE_RECOGNITION_START: 812 processLocalVrEvent(HeadsetHalConstants.VR_STATE_STARTED); 813 break; 814 case VOICE_RECOGNITION_STOP: 815 processLocalVrEvent(HeadsetHalConstants.VR_STATE_STOPPED); 816 break; 817 case INTENT_SCO_VOLUME_CHANGED: 818 processIntentScoVolume((Intent) message.obj); 819 break; 820 case CALL_STATE_CHANGED: 821 processCallState((HeadsetCallState) message.obj); 822 break; 823 case INTENT_BATTERY_CHANGED: 824 processIntentBatteryChanged((Intent) message.obj); 825 break; 826 case ROAM_CHANGED: 827 processRoamChanged((Boolean) message.obj); 828 break; 829 case DEVICE_STATE_CHANGED: 830 processDeviceStateChanged((HeadsetDeviceState) message.obj); 831 break; 832 case SEND_CCLC_RESPONSE: 833 processSendClccResponse((HeadsetClccResponse) message.obj); 834 break; 835 case DIALING_OUT_TIMEOUT: 836 if (mDialingOut) { 837 mDialingOut= false; 838 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 839 } 840 break; 841 case START_VR_TIMEOUT: 842 if (mWaitingForVoiceRecognition) { 843 mWaitingForVoiceRecognition = false; 844 Log.e(TAG, "Timeout waiting for voice recognition to start"); 845 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 846 } 847 break; 848 case STACK_EVENT: 849 StackEvent event = (StackEvent) message.obj; 850 if (DBG) { 851 log("event type: " + event.type); 852 } 853 switch (event.type) { 854 case EVENT_TYPE_CONNECTION_STATE_CHANGED: 855 processConnectionEvent(event.valueInt, event.device); 856 break; 857 case EVENT_TYPE_AUDIO_STATE_CHANGED: 858 processAudioEvent(event.valueInt, event.device); 859 break; 860 case EVENT_TYPE_VR_STATE_CHANGED: 861 processVrEvent(event.valueInt); 862 break; 863 case EVENT_TYPE_ANSWER_CALL: 864 processAnswerCall(); 865 break; 866 case EVENT_TYPE_HANGUP_CALL: 867 processHangupCall(); 868 break; 869 case EVENT_TYPE_VOLUME_CHANGED: 870 processVolumeEvent(event.valueInt, event.valueInt2); 871 break; 872 case EVENT_TYPE_DIAL_CALL: 873 processDialCall(event.valueString); 874 break; 875 case EVENT_TYPE_SEND_DTMF: 876 processSendDtmf(event.valueInt); 877 break; 878 case EVENT_TYPE_NOICE_REDUCTION: 879 processNoiceReductionEvent(event.valueInt); 880 break; 881 case EVENT_TYPE_AT_CHLD: 882 processAtChld(event.valueInt); 883 break; 884 case EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST: 885 processSubscriberNumberRequest(); 886 break; 887 case EVENT_TYPE_AT_CIND: 888 processAtCind(); 889 break; 890 case EVENT_TYPE_AT_COPS: 891 processAtCops(); 892 break; 893 case EVENT_TYPE_AT_CLCC: 894 processAtClcc(); 895 break; 896 case EVENT_TYPE_UNKNOWN_AT: 897 processUnknownAt(event.valueString); 898 break; 899 case EVENT_TYPE_KEY_PRESSED: 900 processKeyPressed(); 901 break; 902 default: 903 Log.e(TAG, "Unknown stack event: " + event.type); 904 break; 905 } 906 break; 907 default: 908 return NOT_HANDLED; 909 } 910 return retValue; 911 } 912 913 // in AudioOn state. Some headsets disconnect RFCOMM prior to SCO down. Handle this 914 private void processConnectionEvent(int state, BluetoothDevice device) { 915 switch (state) { 916 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED: 917 if (mCurrentDevice.equals(device)) { 918 processAudioEvent (HeadsetHalConstants.AUDIO_STATE_DISCONNECTED, device); 919 broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED, 920 BluetoothProfile.STATE_CONNECTED); 921 synchronized (HeadsetStateMachine.this) { 922 mCurrentDevice = null; 923 transitionTo(mDisconnected); 924 } 925 } else { 926 Log.e(TAG, "Disconnected from unknown device: " + device); 927 } 928 break; 929 default: 930 Log.e(TAG, "Connection State Device: " + device + " bad state: " + state); 931 break; 932 } 933 } 934 935 // in AudioOn state 936 private void processAudioEvent(int state, BluetoothDevice device) { 937 if (!mCurrentDevice.equals(device)) { 938 Log.e(TAG, "Audio changed on disconnected device: " + device); 939 return; 940 } 941 942 switch (state) { 943 case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED: 944 if (mAudioState != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { 945 mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 946 mAudioManager.setBluetoothScoOn(false); 947 broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 948 BluetoothHeadset.STATE_AUDIO_CONNECTED); 949 } 950 transitionTo(mConnected); 951 break; 952 case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING: 953 // TODO(BT) adding STATE_AUDIO_DISCONNECTING in BluetoothHeadset? 954 //broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_DISCONNECTING, 955 // BluetoothHeadset.STATE_AUDIO_CONNECTED); 956 break; 957 default: 958 Log.e(TAG, "Audio State Device: " + device + " bad state: " + state); 959 break; 960 } 961 } 962 963 private void processIntentScoVolume(Intent intent) { 964 int volumeValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0); 965 if (mPhoneState.getSpeakerVolume() != volumeValue) { 966 mPhoneState.setSpeakerVolume(volumeValue); 967 setVolumeNative(HeadsetHalConstants.VOLUME_TYPE_SPK, volumeValue); 968 } 969 } 970 } 971 972 private ServiceConnection mConnection = new ServiceConnection() { 973 public void onServiceConnected(ComponentName className, IBinder service) { 974 if (DBG) Log.d(TAG, "Proxy object connected"); 975 mPhoneProxy = IBluetoothHeadsetPhone.Stub.asInterface(service); 976 } 977 978 public void onServiceDisconnected(ComponentName className) { 979 if (DBG) Log.d(TAG, "Proxy object disconnected"); 980 mPhoneProxy = null; 981 } 982 }; 983 984 // HFP Connection state of the device could be changed by the state machine 985 // in separate thread while this method is executing. 986 int getConnectionState(BluetoothDevice device) { 987 if (getCurrentState() == mDisconnected) { 988 return BluetoothProfile.STATE_DISCONNECTED; 989 } 990 991 synchronized (this) { 992 IState currentState = getCurrentState(); 993 if (currentState == mPending) { 994 if ((mTargetDevice != null) && mTargetDevice.equals(device)) { 995 return BluetoothProfile.STATE_CONNECTING; 996 } 997 if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) { 998 return BluetoothProfile.STATE_DISCONNECTING; 999 } 1000 if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) { 1001 return BluetoothProfile.STATE_CONNECTING; // incoming connection 1002 } 1003 return BluetoothProfile.STATE_DISCONNECTED; 1004 } 1005 1006 if (currentState == mConnected || currentState == mAudioOn) { 1007 if (mCurrentDevice.equals(device)) { 1008 return BluetoothProfile.STATE_CONNECTED; 1009 } 1010 return BluetoothProfile.STATE_DISCONNECTED; 1011 } else { 1012 Log.e(TAG, "Bad currentState: " + currentState); 1013 return BluetoothProfile.STATE_DISCONNECTED; 1014 } 1015 } 1016 } 1017 1018 List<BluetoothDevice> getConnectedDevices() { 1019 List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); 1020 synchronized(this) { 1021 if (isConnected()) { 1022 devices.add(mCurrentDevice); 1023 } 1024 } 1025 return devices; 1026 } 1027 1028 boolean isAudioOn() { 1029 return (getCurrentState() == mAudioOn); 1030 } 1031 1032 boolean isAudioConnected(BluetoothDevice device) { 1033 synchronized(this) { 1034 1035 /* Additional check for audio state included for the case when PhoneApp queries 1036 Bluetooth Audio state, before we receive the close event from the stack for the 1037 sco disconnect issued in AudioOn state. This was causing a mismatch in the 1038 Incall screen UI. */ 1039 1040 if (getCurrentState() == mAudioOn && mCurrentDevice.equals(device) 1041 && mAudioState != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) 1042 { 1043 return true; 1044 } 1045 } 1046 return false; 1047 } 1048 1049 int getAudioState(BluetoothDevice device) { 1050 synchronized(this) { 1051 if (mCurrentDevice == null || !mCurrentDevice.equals(device)) { 1052 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 1053 } 1054 } 1055 return mAudioState; 1056 } 1057 1058 private void processVrEvent(int state) { 1059 Log.d(TAG, "processVrEvent: state=" + state + " mVoiceRecognitionStarted: " + 1060 mVoiceRecognitionStarted + " mWaitingforVoiceRecognition: " + mWaitingForVoiceRecognition + 1061 " isInCall: " + isInCall()); 1062 if (state == HeadsetHalConstants.VR_STATE_STARTED) { 1063 // TODO(BT) handle virtualcall 1064 if (!mVoiceRecognitionStarted && 1065 !isInCall()) 1066 { 1067 try { 1068 mService.startActivity(sVoiceCommandIntent); 1069 } catch (ActivityNotFoundException e) { 1070 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1071 return; 1072 } 1073 expectVoiceRecognition(); 1074 } 1075 } else if (state == HeadsetHalConstants.VR_STATE_STOPPED) { 1076 if (mVoiceRecognitionStarted || mWaitingForVoiceRecognition) 1077 { 1078 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0); 1079 mVoiceRecognitionStarted = false; 1080 mWaitingForVoiceRecognition = false; 1081 if (!isInCall()) { 1082 disconnectAudioNative(getByteAddress(mCurrentDevice)); 1083 mAudioManager.setParameters("A2dpSuspended=false"); 1084 } 1085 } 1086 else 1087 { 1088 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1089 } 1090 } else { 1091 Log.e(TAG, "Bad Voice Recognition state: " + state); 1092 } 1093 } 1094 1095 private void processLocalVrEvent(int state) 1096 { 1097 if (state == HeadsetHalConstants.VR_STATE_STARTED) 1098 { 1099 boolean needAudio = true; 1100 if (mVoiceRecognitionStarted || isInCall()) 1101 { 1102 Log.e(TAG, "Voice recognition started when call is active. isInCall:" + isInCall() + 1103 " mVoiceRecognitionStarted: " + mVoiceRecognitionStarted); 1104 return; 1105 } 1106 mVoiceRecognitionStarted = true; 1107 1108 if (mWaitingForVoiceRecognition) 1109 { 1110 Log.d(TAG, "Voice recognition started successfully"); 1111 mWaitingForVoiceRecognition = false; 1112 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0); 1113 removeMessages(START_VR_TIMEOUT); 1114 } 1115 else 1116 { 1117 Log.d(TAG, "Voice recognition started locally"); 1118 needAudio = startVoiceRecognitionNative(); 1119 } 1120 1121 if (needAudio && !isAudioOn()) 1122 { 1123 Log.d(TAG, "Initiating audio connection for Voice Recognition"); 1124 // At this stage, we need to be sure that AVDTP is not streaming. This is needed 1125 // to be compliant with the AV+HFP Whitepaper as we cannot have A2DP in 1126 // streaming state while a SCO connection is established. 1127 // This is needed for VoiceDial scenario alone and not for 1128 // incoming call/outgoing call scenarios as the phone enters MODE_RINGTONE 1129 // or MODE_IN_CALL which shall automatically suspend the AVDTP stream if needed. 1130 // Whereas for VoiceDial we want to activate the SCO connection but we are still 1131 // in MODE_NORMAL and hence the need to explicitly suspend the A2DP stream 1132 mAudioManager.setParameters("A2dpSuspended=true"); 1133 connectAudioNative(getByteAddress(mCurrentDevice)); 1134 } 1135 1136 if (mStartVoiceRecognitionWakeLock.isHeld()) { 1137 mStartVoiceRecognitionWakeLock.release(); 1138 } 1139 } 1140 else 1141 { 1142 Log.d(TAG, "Voice Recognition stopped. mVoiceRecognitionStarted: " + mVoiceRecognitionStarted + 1143 " mWaitingForVoiceRecognition: " + mWaitingForVoiceRecognition); 1144 if (mVoiceRecognitionStarted || mWaitingForVoiceRecognition) 1145 { 1146 mVoiceRecognitionStarted = false; 1147 mWaitingForVoiceRecognition = false; 1148 1149 if (stopVoiceRecognitionNative() && !isInCall()) { 1150 disconnectAudioNative(getByteAddress(mCurrentDevice)); 1151 mAudioManager.setParameters("A2dpSuspended=false"); 1152 } 1153 } 1154 } 1155 } 1156 1157 private synchronized void expectVoiceRecognition() { 1158 mWaitingForVoiceRecognition = true; 1159 sendMessageDelayed(START_VR_TIMEOUT, START_VR_TIMEOUT_VALUE); 1160 if (!mStartVoiceRecognitionWakeLock.isHeld()) { 1161 mStartVoiceRecognitionWakeLock.acquire(START_VR_TIMEOUT_VALUE); 1162 } 1163 } 1164 1165 List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 1166 List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(); 1167 Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices(); 1168 int connectionState; 1169 synchronized (this) { 1170 for (BluetoothDevice device : bondedDevices) { 1171 ParcelUuid[] featureUuids = device.getUuids(); 1172 if (!BluetoothUuid.containsAnyUuid(featureUuids, HEADSET_UUIDS)) { 1173 continue; 1174 } 1175 connectionState = getConnectionState(device); 1176 for(int i = 0; i < states.length; i++) { 1177 if (connectionState == states[i]) { 1178 deviceList.add(device); 1179 } 1180 } 1181 } 1182 } 1183 return deviceList; 1184 } 1185 1186 // This method does not check for error conditon (newState == prevState) 1187 private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) { 1188 if (DBG) log("Connection state " + device + ": " + prevState + "->" + newState); 1189 /* Notifying the connection state change of the profile before sending the intent for 1190 connection state change, as it was causing a race condition, with the UI not being 1191 updated with the correct connection state. */ 1192 mService.notifyProfileConnectionStateChanged(device, BluetoothProfile.HEADSET, 1193 newState, prevState); 1194 Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 1195 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); 1196 intent.putExtra(BluetoothProfile.EXTRA_STATE, newState); 1197 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 1198 mService.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM); 1199 } 1200 1201 private void broadcastAudioState(BluetoothDevice device, int newState, int prevState) { 1202 Intent intent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); 1203 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); 1204 intent.putExtra(BluetoothProfile.EXTRA_STATE, newState); 1205 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 1206 mService.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM); 1207 if (DBG) log("Audio state " + device + ": " + prevState + "->" + newState); 1208 } 1209 1210 private void configAudioParameters() 1211 { 1212 // Reset NREC on connect event. Headset will override later 1213 mAudioManager.setParameters(HEADSET_NAME + "=" + getCurrentDeviceName() + ";" + 1214 HEADSET_NREC + "=on"); 1215 } 1216 1217 private String parseUnknownAt(String atString) 1218 { 1219 StringBuilder atCommand = new StringBuilder(atString.length()); 1220 String result = null; 1221 1222 for (int i = 0; i < atString.length(); i++) { 1223 char c = atString.charAt(i); 1224 if (c == '"') { 1225 int j = atString.indexOf('"', i + 1 ); // search for closing " 1226 if (j == -1) { // unmatched ", insert one. 1227 atCommand.append(atString.substring(i, atString.length())); 1228 atCommand.append('"'); 1229 break; 1230 } 1231 atCommand.append(atString.substring(i, j + 1)); 1232 i = j; 1233 } else if (c != ' ') { 1234 atCommand.append(Character.toUpperCase(c)); 1235 } 1236 } 1237 result = atCommand.toString(); 1238 return result; 1239 } 1240 1241 private int getAtCommandType(String atCommand) 1242 { 1243 int commandType = mPhonebook.TYPE_UNKNOWN; 1244 String atString = null; 1245 atCommand = atCommand.trim(); 1246 if (atCommand.length() > 5) 1247 { 1248 atString = atCommand.substring(5); 1249 if (atString.startsWith("?")) // Read 1250 commandType = mPhonebook.TYPE_READ; 1251 else if (atString.startsWith("=?")) // Test 1252 commandType = mPhonebook.TYPE_TEST; 1253 else if (atString.startsWith("=")) // Set 1254 commandType = mPhonebook.TYPE_SET; 1255 else 1256 commandType = mPhonebook.TYPE_UNKNOWN; 1257 } 1258 return commandType; 1259 } 1260 1261 1262 private void processAnswerCall() { 1263 if (mPhoneProxy != null) { 1264 try { 1265 mPhoneProxy.answerCall(); 1266 } catch (RemoteException e) { 1267 Log.e(TAG, Log.getStackTraceString(new Throwable())); 1268 } 1269 } else { 1270 Log.e(TAG, "Handsfree phone proxy null for answering call"); 1271 } 1272 } 1273 1274 private void processHangupCall() { 1275 if (mPhoneProxy != null) { 1276 try { 1277 mPhoneProxy.hangupCall(); 1278 } catch (RemoteException e) { 1279 Log.e(TAG, Log.getStackTraceString(new Throwable())); 1280 } 1281 } else { 1282 Log.e(TAG, "Handsfree phone proxy null for hanging up call"); 1283 } 1284 } 1285 1286 private void processDialCall(String number) { 1287 String dialNumber; 1288 if ((number == null) || (number.length() == 0)) { 1289 dialNumber = mPhonebook.getLastDialledNumber(); 1290 if (dialNumber == null) { 1291 if (DBG) log("processDialCall, last dial number null"); 1292 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1293 return; 1294 } 1295 } else if (number.charAt(0) == '>') { 1296 // Yuck - memory dialling requested. 1297 // Just dial last number for now 1298 if (number.startsWith(">9999")) { // for PTS test 1299 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1300 return; 1301 } 1302 if (DBG) log("processDialCall, memory dial do last dial for now"); 1303 dialNumber = mPhonebook.getLastDialledNumber(); 1304 if (dialNumber == null) { 1305 if (DBG) log("processDialCall, last dial number null"); 1306 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1307 return; 1308 } 1309 } else { 1310 // Remove trailing ';' 1311 if (number.charAt(number.length() - 1) == ';') { 1312 number = number.substring(0, number.length() - 1); 1313 } 1314 1315 dialNumber = PhoneNumberUtils.convertPreDial(number); 1316 } 1317 // TODO(BT) do we need to terminate virtual call first 1318 // like call terminateScoUsingVirtualVoiceCall()? 1319 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, 1320 Uri.fromParts(SCHEME_TEL, dialNumber, null)); 1321 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1322 mService.startActivity(intent); 1323 // TODO(BT) continue send OK reults code after call starts 1324 // hold wait lock, start a timer, set wait call flag 1325 // Get call started indication from bluetooth phone 1326 mDialingOut = true; 1327 sendMessageDelayed(DIALING_OUT_TIMEOUT, DIALING_OUT_TIMEOUT_VALUE); 1328 } 1329 1330 private void processVolumeEvent(int volumeType, int volume) { 1331 if (volumeType == HeadsetHalConstants.VOLUME_TYPE_SPK) { 1332 mPhoneState.setSpeakerVolume(volume); 1333 int flag = (getCurrentState() == mAudioOn) ? AudioManager.FLAG_SHOW_UI : 0; 1334 mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, volume, flag); 1335 } else if (volumeType == HeadsetHalConstants.VOLUME_TYPE_MIC) { 1336 mPhoneState.setMicVolume(volume); 1337 } else { 1338 Log.e(TAG, "Bad voluem type: " + volumeType); 1339 } 1340 } 1341 1342 private void processSendDtmf(int dtmf) { 1343 if (mPhoneProxy != null) { 1344 try { 1345 mPhoneProxy.sendDtmf(dtmf); 1346 } catch (RemoteException e) { 1347 Log.e(TAG, Log.getStackTraceString(new Throwable())); 1348 } 1349 } else { 1350 Log.e(TAG, "Handsfree phone proxy null for sending DTMF"); 1351 } 1352 } 1353 1354 private void processCallState(HeadsetCallState callState) { 1355 mPhoneState.setNumActiveCall(callState.mNumActive); 1356 mPhoneState.setNumHeldCall(callState.mNumHeld); 1357 mPhoneState.setCallState(callState.mCallState); 1358 if (mDialingOut && callState.mCallState == HeadsetHalConstants.CALL_STATE_DIALING) { 1359 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0); 1360 removeMessages(DIALING_OUT_TIMEOUT); 1361 mDialingOut = false; 1362 } 1363 log("mNumActive: " + callState.mNumActive + " mNumHeld: " + callState.mNumHeld + 1364 " mCallState: " + callState.mCallState); 1365 log("mNumber: " + callState.mNumber + " mType: " + callState.mType); 1366 if (getCurrentState() != mDisconnected) { 1367 phoneStateChangeNative(callState.mNumActive, callState.mNumHeld, callState.mCallState, 1368 callState.mNumber, callState.mType); 1369 } 1370 } 1371 1372 // enable 1 enable noice reduction 1373 // 0 disable noice reduction 1374 private void processNoiceReductionEvent(int enable) { 1375 if (enable == 1) { 1376 mAudioManager.setParameters(HEADSET_NREC + "=on"); 1377 } else { 1378 mAudioManager.setParameters(HEADSET_NREC + "off"); 1379 } 1380 } 1381 1382 private void processAtChld(int chld) { 1383 if (mPhoneProxy != null) { 1384 try { 1385 if (mPhoneProxy.processChld(chld)) { 1386 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0); 1387 } else { 1388 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1389 } 1390 } catch (RemoteException e) { 1391 Log.e(TAG, Log.getStackTraceString(new Throwable())); 1392 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1393 } 1394 } else { 1395 Log.e(TAG, "Handsfree phone proxy null for At+Chld"); 1396 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1397 } 1398 } 1399 1400 private void processSubscriberNumberRequest() { 1401 if (mPhoneProxy != null) { 1402 try { 1403 String number = mPhoneProxy.getSubscriberNumber(); 1404 if (number != null) { 1405 atResponseStringNative("+CNUM: ,\"" + number + "\"," + 1406 PhoneNumberUtils.toaFromString(number) + ",,4"); 1407 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0); 1408 } 1409 } catch (RemoteException e) { 1410 Log.e(TAG, Log.getStackTraceString(new Throwable())); 1411 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1412 } 1413 } else { 1414 Log.e(TAG, "Handsfree phone proxy null for At+CNUM"); 1415 } 1416 } 1417 1418 private void processAtCind() { 1419 cindResponseNative(mPhoneState.getService(), mPhoneState.getNumActiveCall(), 1420 mPhoneState.getNumHeldCall(), mPhoneState.getCallState(), 1421 mPhoneState.getSignal(), mPhoneState.getRoam(), 1422 mPhoneState.getBatteryCharge()); 1423 } 1424 1425 private void processAtCops() { 1426 if (mPhoneProxy != null) { 1427 try { 1428 String operatorName = mPhoneProxy.getNetworkOperator(); 1429 if (operatorName == null) { 1430 operatorName = ""; 1431 } 1432 copsResponseNative(operatorName); 1433 } catch (RemoteException e) { 1434 Log.e(TAG, Log.getStackTraceString(new Throwable())); 1435 copsResponseNative(""); 1436 } 1437 } else { 1438 Log.e(TAG, "Handsfree phone proxy null for At+COPS"); 1439 copsResponseNative(""); 1440 } 1441 } 1442 1443 private void processAtClcc() { 1444 if (mPhoneProxy != null) { 1445 try { 1446 if (!mPhoneProxy.listCurrentCalls()) { 1447 clccResponseNative(0, 0, 0, 0, false, "", 0); 1448 } 1449 } catch (RemoteException e) { 1450 Log.e(TAG, Log.getStackTraceString(new Throwable())); 1451 clccResponseNative(0, 0, 0, 0, false, "", 0); 1452 } 1453 } else { 1454 Log.e(TAG, "Handsfree phone proxy null for At+CLCC"); 1455 clccResponseNative(0, 0, 0, 0, false, "", 0); 1456 } 1457 } 1458 1459 private void processAtCscs(String atString, int type) { 1460 log("processAtCscs - atString = "+ atString); 1461 if(mPhonebook != null) { 1462 mPhonebook.handleCscsCommand(atString, type); 1463 } 1464 else { 1465 Log.e(TAG, "Phonebook handle null for At+CSCS"); 1466 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1467 } 1468 } 1469 1470 private void processAtCpbs(String atString, int type) { 1471 log("processAtCpbs - atString = "+ atString); 1472 if(mPhonebook != null) { 1473 mPhonebook.handleCpbsCommand(atString, type); 1474 } 1475 else { 1476 Log.e(TAG, "Phonebook handle null for At+CPBS"); 1477 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1478 } 1479 } 1480 1481 private void processAtCpbr(String atString, int type, BluetoothDevice mCurrentDevice) { 1482 log("processAtCpbr - atString = "+ atString); 1483 if(mPhonebook != null) { 1484 mPhonebook.handleCpbrCommand(atString, type, mCurrentDevice); 1485 } 1486 else { 1487 Log.e(TAG, "Phonebook handle null for At+CPBR"); 1488 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1489 } 1490 } 1491 1492 private void processUnknownAt(String atString) { 1493 // TODO (BT) 1494 log("processUnknownAt - atString = "+ atString); 1495 String atCommand = parseUnknownAt(atString); 1496 int commandType = getAtCommandType(atCommand); 1497 if (atCommand.startsWith("+CSCS")) 1498 processAtCscs(atCommand.substring(5), commandType); 1499 else if (atCommand.startsWith("+CPBS")) 1500 processAtCpbs(atCommand.substring(5), commandType); 1501 else if (atCommand.startsWith("+CPBR")) 1502 processAtCpbr(atCommand.substring(5), commandType, mCurrentDevice); 1503 else 1504 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1505 } 1506 1507 private void processKeyPressed() { 1508 if (mPhoneState.getCallState() == HeadsetHalConstants.CALL_STATE_INCOMING) { 1509 if (mPhoneProxy != null) { 1510 try { 1511 mPhoneProxy.answerCall(); 1512 } catch (RemoteException e) { 1513 Log.e(TAG, Log.getStackTraceString(new Throwable())); 1514 } 1515 } else { 1516 Log.e(TAG, "Handsfree phone proxy null for answering call"); 1517 } 1518 } else if (mPhoneState.getNumActiveCall() > 0) { 1519 if (!isAudioOn()) 1520 { 1521 connectAudioNative(getByteAddress(mCurrentDevice)); 1522 } 1523 else 1524 { 1525 if (mPhoneProxy != null) { 1526 try { 1527 mPhoneProxy.hangupCall(); 1528 } catch (RemoteException e) { 1529 Log.e(TAG, Log.getStackTraceString(new Throwable())); 1530 } 1531 } else { 1532 Log.e(TAG, "Handsfree phone proxy null for hangup call"); 1533 } 1534 } 1535 } else { 1536 String dialNumber = mPhonebook.getLastDialledNumber(); 1537 if (dialNumber == null) { 1538 if (DBG) log("processKeyPressed, last dial number null"); 1539 return; 1540 } 1541 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, 1542 Uri.fromParts(SCHEME_TEL, dialNumber, null)); 1543 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1544 mService.startActivity(intent); 1545 } 1546 } 1547 1548 private void onConnectionStateChanged(int state, byte[] address) { 1549 StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED); 1550 event.valueInt = state; 1551 event.device = getDevice(address); 1552 sendMessage(STACK_EVENT, event); 1553 } 1554 1555 private void onAudioStateChanged(int state, byte[] address) { 1556 StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED); 1557 event.valueInt = state; 1558 event.device = getDevice(address); 1559 sendMessage(STACK_EVENT, event); 1560 } 1561 1562 private void onVrStateChanged(int state) { 1563 StackEvent event = new StackEvent(EVENT_TYPE_VR_STATE_CHANGED); 1564 event.valueInt = state; 1565 sendMessage(STACK_EVENT, event); 1566 } 1567 1568 private void onAnswerCall() { 1569 StackEvent event = new StackEvent(EVENT_TYPE_ANSWER_CALL); 1570 sendMessage(STACK_EVENT, event); 1571 } 1572 1573 private void onHangupCall() { 1574 StackEvent event = new StackEvent(EVENT_TYPE_HANGUP_CALL); 1575 sendMessage(STACK_EVENT, event); 1576 } 1577 1578 private void onVolumeChanged(int type, int volume) { 1579 StackEvent event = new StackEvent(EVENT_TYPE_VOLUME_CHANGED); 1580 event.valueInt = type; 1581 event.valueInt2 = volume; 1582 sendMessage(STACK_EVENT, event); 1583 } 1584 1585 private void onDialCall(String number) { 1586 StackEvent event = new StackEvent(EVENT_TYPE_DIAL_CALL); 1587 event.valueString = number; 1588 sendMessage(STACK_EVENT, event); 1589 } 1590 1591 private void onSendDtmf(int dtmf) { 1592 StackEvent event = new StackEvent(EVENT_TYPE_SEND_DTMF); 1593 event.valueInt = dtmf; 1594 sendMessage(STACK_EVENT, event); 1595 } 1596 1597 private void onNoiceReductionEnable(boolean enable) { 1598 StackEvent event = new StackEvent(EVENT_TYPE_NOICE_REDUCTION); 1599 event.valueInt = enable ? 1 : 0; 1600 sendMessage(STACK_EVENT, event); 1601 } 1602 1603 private void onAtChld(int chld) { 1604 StackEvent event = new StackEvent(EVENT_TYPE_AT_CHLD); 1605 event.valueInt = chld; 1606 sendMessage(STACK_EVENT, event); 1607 } 1608 1609 private void onAtCnum() { 1610 StackEvent event = new StackEvent(EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST); 1611 sendMessage(STACK_EVENT, event); 1612 } 1613 1614 private void onAtCind() { 1615 StackEvent event = new StackEvent(EVENT_TYPE_AT_CIND); 1616 sendMessage(STACK_EVENT, event); 1617 } 1618 1619 private void onAtCops() { 1620 StackEvent event = new StackEvent(EVENT_TYPE_AT_COPS); 1621 sendMessage(STACK_EVENT, event); 1622 } 1623 1624 private void onAtClcc() { 1625 StackEvent event = new StackEvent(EVENT_TYPE_AT_CLCC); 1626 sendMessage(STACK_EVENT, event); 1627 } 1628 1629 private void onUnknownAt(String atString) { 1630 StackEvent event = new StackEvent(EVENT_TYPE_UNKNOWN_AT); 1631 event.valueString = atString; 1632 sendMessage(STACK_EVENT, event); 1633 } 1634 1635 private void onKeyPressed() { 1636 StackEvent event = new StackEvent(EVENT_TYPE_KEY_PRESSED); 1637 sendMessage(STACK_EVENT, event); 1638 } 1639 1640 private void processIntentBatteryChanged(Intent intent) { 1641 int batteryLevel = intent.getIntExtra("level", -1); 1642 int scale = intent.getIntExtra("scale", -1); 1643 if (batteryLevel == -1 || scale == -1 || scale == 0) { 1644 Log.e(TAG, "Bad Battery Changed intent: " + batteryLevel + "," + scale); 1645 return; 1646 } 1647 batteryLevel = batteryLevel * 5 / scale; 1648 mPhoneState.setBatteryCharge(batteryLevel); 1649 } 1650 1651 private void processRoamChanged(boolean roam) { 1652 mPhoneState.setRoam(roam ? HeadsetHalConstants.SERVICE_TYPE_ROAMING : 1653 HeadsetHalConstants.SERVICE_TYPE_HOME); 1654 } 1655 1656 private void processDeviceStateChanged(HeadsetDeviceState deviceState) { 1657 notifyDeviceStatusNative(deviceState.mService, deviceState.mRoam, deviceState.mSignal, 1658 deviceState.mBatteryCharge); 1659 } 1660 1661 private void processSendClccResponse(HeadsetClccResponse clcc) { 1662 clccResponseNative(clcc.mIndex, clcc.mDirection, clcc.mStatus, clcc.mMode, clcc.mMpty, 1663 clcc.mNumber, clcc.mType); 1664 } 1665 1666 private String getCurrentDeviceName() { 1667 String defaultName = "<unknown>"; 1668 if (mCurrentDevice == null) { 1669 return defaultName; 1670 } 1671 String deviceName = mCurrentDevice.getName(); 1672 if (deviceName == null) { 1673 return defaultName; 1674 } 1675 return deviceName; 1676 } 1677 1678 private byte[] getByteAddress(BluetoothDevice device) { 1679 return Utils.getBytesFromAddress(device.getAddress()); 1680 } 1681 1682 private BluetoothDevice getDevice(byte[] address) { 1683 return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address)); 1684 } 1685 1686 private boolean isInCall() { 1687 return ((mPhoneState.getNumActiveCall() > 0) || (mPhoneState.getNumHeldCall() > 0) || 1688 (mPhoneState.getCallState() != HeadsetHalConstants.CALL_STATE_IDLE)); 1689 } 1690 1691 boolean isConnected() { 1692 IState currentState = getCurrentState(); 1693 return (currentState == mConnected || currentState == mAudioOn); 1694 } 1695 1696 private void log(String msg) { 1697 if (DBG) { 1698 Log.d(TAG, msg); 1699 } 1700 } 1701 1702 public void handleAccessPermissionResult(Intent intent) { 1703 log("handleAccessPermissionResult"); 1704 if(mPhonebook != null) { 1705 if (!mPhonebook.getCheckingAccessPermission()) { 1706 return; 1707 } 1708 int atCommandResult = 0; 1709 int atCommandErrorCode = 0; 1710 //HeadsetBase headset = mHandsfree.getHeadset(); 1711 // ASSERT: (headset != null) && headSet.isConnected() 1712 // REASON: mCheckingAccessPermission is true, otherwise resetAtState 1713 // has set mCheckingAccessPermission to false 1714 if (intent.getAction().equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) { 1715 if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT, 1716 BluetoothDevice.CONNECTION_ACCESS_NO) == 1717 BluetoothDevice.CONNECTION_ACCESS_YES) { 1718 if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) { 1719 mCurrentDevice.setTrust(true); 1720 } 1721 atCommandResult = mPhonebook.processCpbrCommand(); 1722 } 1723 } 1724 mPhonebook.setCpbrIndex(-1); 1725 mPhonebook.setCheckingAccessPermission(false); 1726 1727 if (atCommandResult >= 0) { 1728 atResponseCodeNative(atCommandResult, atCommandErrorCode); 1729 } 1730 else 1731 log("handleAccessPermissionResult - RESULT_NONE"); 1732 } 1733 else { 1734 Log.e(TAG, "Phonebook handle null"); 1735 atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1736 } 1737 } 1738 1739 private static final String SCHEME_TEL = "tel"; 1740 1741 // Event types for STACK_EVENT message 1742 final private static int EVENT_TYPE_NONE = 0; 1743 final private static int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1; 1744 final private static int EVENT_TYPE_AUDIO_STATE_CHANGED = 2; 1745 final private static int EVENT_TYPE_VR_STATE_CHANGED = 3; 1746 final private static int EVENT_TYPE_ANSWER_CALL = 4; 1747 final private static int EVENT_TYPE_HANGUP_CALL = 5; 1748 final private static int EVENT_TYPE_VOLUME_CHANGED = 6; 1749 final private static int EVENT_TYPE_DIAL_CALL = 7; 1750 final private static int EVENT_TYPE_SEND_DTMF = 8; 1751 final private static int EVENT_TYPE_NOICE_REDUCTION = 9; 1752 final private static int EVENT_TYPE_AT_CHLD = 10; 1753 final private static int EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST = 11; 1754 final private static int EVENT_TYPE_AT_CIND = 12; 1755 final private static int EVENT_TYPE_AT_COPS = 13; 1756 final private static int EVENT_TYPE_AT_CLCC = 14; 1757 final private static int EVENT_TYPE_UNKNOWN_AT = 15; 1758 final private static int EVENT_TYPE_KEY_PRESSED = 16; 1759 1760 private class StackEvent { 1761 int type = EVENT_TYPE_NONE; 1762 int valueInt = 0; 1763 int valueInt2 = 0; 1764 String valueString = null; 1765 BluetoothDevice device = null; 1766 1767 private StackEvent(int type) { 1768 this.type = type; 1769 } 1770 } 1771 1772 /*package*/native boolean atResponseCodeNative(int responseCode, int errorCode); 1773 /*package*/ native boolean atResponseStringNative(String responseString); 1774 1775 private native static void classInitNative(); 1776 private native void initializeNative(); 1777 private native void cleanupNative(); 1778 private native boolean connectHfpNative(byte[] address); 1779 private native boolean disconnectHfpNative(byte[] address); 1780 private native boolean connectAudioNative(byte[] address); 1781 private native boolean disconnectAudioNative(byte[] address); 1782 private native boolean startVoiceRecognitionNative(); 1783 private native boolean stopVoiceRecognitionNative(); 1784 private native boolean setVolumeNative(int volumeType, int volume); 1785 private native boolean cindResponseNative(int service, int numActive, int numHeld, 1786 int callState, int signal, int roam, 1787 int batteryCharge); 1788 private native boolean notifyDeviceStatusNative(int networkState, int serviceType, int signal, 1789 int batteryCharge); 1790 1791 private native boolean clccResponseNative(int index, int dir, int status, int mode, 1792 boolean mpty, String number, int type); 1793 private native boolean copsResponseNative(String operatorName); 1794 1795 private native boolean phoneStateChangeNative(int numActive, int numHeld, int callState, 1796 String number, int type); 1797} 1798