HeadsetStateMachine.java revision dbd53d223fddcad41820c3ded5d36058c5910e7f
1/* 2 * Copyright (C) 2012 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.bluetooth.hfp; 18 19import android.bluetooth.BluetoothAssignedNumbers; 20import android.bluetooth.BluetoothDevice; 21import android.bluetooth.BluetoothHeadset; 22import android.bluetooth.BluetoothProfile; 23import android.content.ActivityNotFoundException; 24import android.content.Context; 25import android.content.Intent; 26import android.media.AudioManager; 27import android.net.Uri; 28import android.os.IDeviceIdleController; 29import android.os.Looper; 30import android.os.Message; 31import android.os.RemoteException; 32import android.os.ServiceManager; 33import android.os.SystemClock; 34import android.os.UserHandle; 35import android.support.annotation.VisibleForTesting; 36import android.telephony.PhoneNumberUtils; 37import android.util.Log; 38 39import com.android.bluetooth.btservice.AdapterService; 40import com.android.bluetooth.btservice.ProfileService; 41import com.android.internal.util.State; 42import com.android.internal.util.StateMachine; 43 44import java.io.FileDescriptor; 45import java.io.PrintWriter; 46import java.io.StringWriter; 47import java.util.ArrayList; 48import java.util.HashMap; 49import java.util.Map; 50import java.util.Objects; 51import java.util.Scanner; 52 53/** 54 * A Bluetooth Handset StateMachine 55 * (Disconnected) 56 * | ^ 57 * CONNECT | | DISCONNECTED 58 * V | 59 * (Connecting) (Disconnecting) 60 * | ^ 61 * CONNECTED | | DISCONNECT 62 * V | 63 * (Connected) 64 * | ^ 65 * CONNECT_AUDIO | | AUDIO_DISCONNECTED 66 * V | 67 * (AudioConnecting) (AudioDiconnecting) 68 * | ^ 69 * AUDIO_CONNECTED | | DISCONNECT_AUDIO 70 * V | 71 * (AudioOn) 72 */ 73@VisibleForTesting 74public class HeadsetStateMachine extends StateMachine { 75 private static final String TAG = "HeadsetStateMachine"; 76 private static final boolean DBG = false; 77 78 private static final String HEADSET_NAME = "bt_headset_name"; 79 private static final String HEADSET_NREC = "bt_headset_nrec"; 80 private static final String HEADSET_WBS = "bt_wbs"; 81 private static final String HEADSET_AUDIO_FEATURE_ON = "on"; 82 private static final String HEADSET_AUDIO_FEATURE_OFF = "off"; 83 84 /* Telephone URI scheme */ 85 private static final String SCHEME_TEL = "tel"; 86 87 static final int CONNECT = 1; 88 static final int DISCONNECT = 2; 89 static final int CONNECT_AUDIO = 3; 90 static final int DISCONNECT_AUDIO = 4; 91 static final int VOICE_RECOGNITION_START = 5; 92 static final int VOICE_RECOGNITION_STOP = 6; 93 94 // message.obj is an intent AudioManager.VOLUME_CHANGED_ACTION 95 // EXTRA_VOLUME_STREAM_TYPE is STREAM_BLUETOOTH_SCO 96 static final int INTENT_SCO_VOLUME_CHANGED = 7; 97 static final int INTENT_CONNECTION_ACCESS_REPLY = 8; 98 static final int CALL_STATE_CHANGED = 9; 99 static final int DEVICE_STATE_CHANGED = 10; 100 static final int SEND_CCLC_RESPONSE = 11; 101 static final int SEND_VENDOR_SPECIFIC_RESULT_CODE = 12; 102 static final int SEND_BSIR = 13; 103 104 static final int VIRTUAL_CALL_START = 14; 105 static final int VIRTUAL_CALL_STOP = 15; 106 107 static final int STACK_EVENT = 101; 108 private static final int DIALING_OUT_TIMEOUT = 102; 109 private static final int START_VR_TIMEOUT = 103; 110 private static final int CLCC_RSP_TIMEOUT = 104; 111 112 private static final int CONNECT_TIMEOUT = 201; 113 114 private static final int DIALING_OUT_TIMEOUT_MS = 10000; 115 private static final int START_VR_TIMEOUT_MS = 5000; 116 private static final int CLCC_RSP_TIMEOUT_MS = 5000; 117 // NOTE: the value is not "final" - it is modified in the unit tests 118 @VisibleForTesting static int sConnectTimeoutMs = 30000; 119 120 private final BluetoothDevice mDevice; 121 122 // State machine states 123 private final Disconnected mDisconnected = new Disconnected(); 124 private final Connecting mConnecting = new Connecting(); 125 private final Disconnecting mDisconnecting = new Disconnecting(); 126 private final Connected mConnected = new Connected(); 127 private final AudioOn mAudioOn = new AudioOn(); 128 private final AudioConnecting mAudioConnecting = new AudioConnecting(); 129 private final AudioDisconnecting mAudioDisconnecting = new AudioDisconnecting(); 130 private HeadsetStateBase mPrevState; 131 132 // Run time dependencies 133 private final HeadsetService mHeadsetService; 134 private final AdapterService mAdapterService; 135 private final HeadsetNativeInterface mNativeInterface; 136 private final HeadsetSystemInterface mSystemInterface; 137 138 // Runtime states 139 private boolean mVirtualCallStarted; 140 private boolean mVoiceRecognitionStarted; 141 private boolean mWaitingForVoiceRecognition; 142 private boolean mDialingOut; 143 private int mSpeakerVolume; 144 private int mMicVolume; 145 // The timestamp when the device entered connecting/connected state 146 private long mConnectingTimestampMs = Long.MIN_VALUE; 147 // Audio Parameters like NREC 148 private final HashMap<String, String> mAudioParams = new HashMap<>(); 149 // AT Phone book keeps a group of states used by AT+CPBR commands 150 private final AtPhonebook mPhonebook; 151 152 // Keys are AT commands, and values are the company IDs. 153 private static final Map<String, Integer> VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID; 154 // Intent that get sent during voice recognition events. 155 private static final Intent VOICE_COMMAND_INTENT; 156 157 static { 158 VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID = new HashMap<>(); 159 VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put( 160 BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT, 161 BluetoothAssignedNumbers.PLANTRONICS); 162 VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put( 163 BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID, 164 BluetoothAssignedNumbers.GOOGLE); 165 VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put( 166 BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XAPL, 167 BluetoothAssignedNumbers.APPLE); 168 VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put( 169 BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV, 170 BluetoothAssignedNumbers.APPLE); 171 VOICE_COMMAND_INTENT = new Intent(Intent.ACTION_VOICE_COMMAND); 172 VOICE_COMMAND_INTENT.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 173 } 174 175 private HeadsetStateMachine(BluetoothDevice device, Looper looper, 176 HeadsetService headsetService, AdapterService adapterService, 177 HeadsetNativeInterface nativeInterface, HeadsetSystemInterface systemInterface) { 178 super(TAG, Objects.requireNonNull(looper, "looper cannot be null")); 179 // Enable/Disable StateMachine debug logs 180 setDbg(DBG); 181 mDevice = Objects.requireNonNull(device, "device cannot be null"); 182 mHeadsetService = Objects.requireNonNull(headsetService, "headsetService cannot be null"); 183 mNativeInterface = 184 Objects.requireNonNull(nativeInterface, "nativeInterface cannot be null"); 185 mSystemInterface = 186 Objects.requireNonNull(systemInterface, "systemInterface cannot be null"); 187 mAdapterService = Objects.requireNonNull(adapterService, "AdapterService cannot be null"); 188 // Create phonebook helper 189 mPhonebook = new AtPhonebook(mHeadsetService, mNativeInterface); 190 // Initialize state machine 191 addState(mDisconnected); 192 addState(mConnecting); 193 addState(mDisconnecting); 194 addState(mConnected); 195 addState(mAudioOn); 196 addState(mAudioConnecting); 197 addState(mAudioDisconnecting); 198 setInitialState(mDisconnected); 199 } 200 201 static HeadsetStateMachine make(BluetoothDevice device, Looper looper, 202 HeadsetService headsetService, AdapterService adapterService, 203 HeadsetNativeInterface nativeInterface, HeadsetSystemInterface systemInterface) { 204 HeadsetStateMachine stateMachine = 205 new HeadsetStateMachine(device, looper, headsetService, adapterService, 206 nativeInterface, systemInterface); 207 stateMachine.start(); 208 Log.i(TAG, "Created state machine " + stateMachine + " for " + device); 209 return stateMachine; 210 } 211 212 static void destroy(HeadsetStateMachine stateMachine) { 213 Log.i(TAG, "destroy"); 214 if (stateMachine == null) { 215 Log.w(TAG, "destroy(), stateMachine is null"); 216 return; 217 } 218 stateMachine.quitNow(); 219 stateMachine.cleanup(); 220 } 221 222 public void cleanup() { 223 if (mPhonebook != null) { 224 mPhonebook.cleanup(); 225 } 226 mAudioParams.clear(); 227 } 228 229 public void dump(StringBuilder sb) { 230 ProfileService.println(sb, " mCurrentDevice: " + mDevice); 231 ProfileService.println(sb, " mCurrentState: " + getCurrentState()); 232 ProfileService.println(sb, " mPrevState: " + mPrevState); 233 ProfileService.println(sb, " mConnectionState: " + getConnectionState()); 234 ProfileService.println(sb, " mAudioState: " + getAudioState()); 235 ProfileService.println(sb, " mVirtualCallStarted: " + mVirtualCallStarted); 236 ProfileService.println(sb, " mVoiceRecognitionStarted: " + mVoiceRecognitionStarted); 237 ProfileService.println(sb, " mWaitingForVoiceRecognition: " + mWaitingForVoiceRecognition); 238 ProfileService.println(sb, " mDialingOut: " + mDialingOut); 239 ProfileService.println(sb, " mSpeakerVolume: " + mSpeakerVolume); 240 ProfileService.println(sb, " mMicVolume: " + mMicVolume); 241 ProfileService.println(sb, 242 " mConnectingTimestampMs(uptimeMillis): " + mConnectingTimestampMs); 243 ProfileService.println(sb, " StateMachine: " + this); 244 // Dump the state machine logs 245 StringWriter stringWriter = new StringWriter(); 246 PrintWriter printWriter = new PrintWriter(stringWriter); 247 super.dump(new FileDescriptor(), printWriter, new String[]{}); 248 printWriter.flush(); 249 stringWriter.flush(); 250 ProfileService.println(sb, " StateMachineLog:"); 251 Scanner scanner = new Scanner(stringWriter.toString()); 252 while (scanner.hasNextLine()) { 253 String line = scanner.nextLine(); 254 ProfileService.println(sb, " " + line); 255 } 256 scanner.close(); 257 } 258 259 /** 260 * Base class for states used in this state machine to share common infrastructures 261 */ 262 private abstract class HeadsetStateBase extends State { 263 @Override 264 public void enter() { 265 // Crash if mPrevState is null and state is not Disconnected 266 if (!(this instanceof Disconnected) && mPrevState == null) { 267 throw new IllegalStateException("mPrevState is null on enter()"); 268 } 269 enforceValidConnectionStateTransition(); 270 } 271 272 @Override 273 public void exit() { 274 mPrevState = this; 275 } 276 277 @Override 278 public String toString() { 279 return getName(); 280 } 281 282 /** 283 * Broadcast audio and connection state changes to the system. This should be called at the 284 * end of enter() method after all the setup is done 285 */ 286 void broadcastStateTransitions() { 287 if (mPrevState == null) { 288 return; 289 } 290 // TODO: Add STATE_AUDIO_DISCONNECTING constant to get rid of the 2nd part of this logic 291 if (getAudioStateInt() != mPrevState.getAudioStateInt() || ( 292 mPrevState instanceof AudioDisconnecting && this instanceof AudioOn)) { 293 stateLogD("audio state changed: " + mDevice + ": " + mPrevState + " -> " + this); 294 broadcastAudioState(mDevice, mPrevState.getAudioStateInt(), getAudioStateInt()); 295 } 296 if (getConnectionStateInt() != mPrevState.getConnectionStateInt()) { 297 stateLogD( 298 "connection state changed: " + mDevice + ": " + mPrevState + " -> " + this); 299 broadcastConnectionState(mDevice, mPrevState.getConnectionStateInt(), 300 getConnectionStateInt()); 301 } 302 } 303 304 // Should not be called from enter() method 305 void broadcastConnectionState(BluetoothDevice device, int fromState, int toState) { 306 stateLogD("broadcastConnectionState " + device + ": " + fromState + "->" + toState); 307 if (fromState == BluetoothProfile.STATE_CONNECTED && isVirtualCallInProgress()) { 308 // Headset is disconnecting, stop Virtual call if active. 309 terminateScoUsingVirtualVoiceCall(); 310 } 311 mHeadsetService.onConnectionStateChangedFromStateMachine(device, fromState, toState); 312 Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 313 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, fromState); 314 intent.putExtra(BluetoothProfile.EXTRA_STATE, toState); 315 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 316 intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 317 mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL, 318 HeadsetService.BLUETOOTH_PERM); 319 } 320 321 // Should not be called from enter() method 322 void broadcastAudioState(BluetoothDevice device, int fromState, int toState) { 323 stateLogD("broadcastAudioState: " + device + ": " + fromState + "->" + toState); 324 if (fromState == BluetoothHeadset.STATE_AUDIO_CONNECTED && isVirtualCallInProgress()) { 325 // When SCO gets disconnected during call transfer, Virtual call 326 // needs to be cleaned up.So call terminateScoUsingVirtualVoiceCall. 327 terminateScoUsingVirtualVoiceCall(); 328 } 329 mHeadsetService.onAudioStateChangedFromStateMachine(device, fromState, toState); 330 Intent intent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); 331 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, fromState); 332 intent.putExtra(BluetoothProfile.EXTRA_STATE, toState); 333 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 334 mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL, 335 HeadsetService.BLUETOOTH_PERM); 336 } 337 338 /** 339 * Verify if the current state transition is legal. This is supposed to be called from 340 * enter() method and crash if the state transition is out of the specification 341 * 342 * Note: 343 * This method uses state objects to verify transition because these objects should be final 344 * and any other instances are invalid 345 */ 346 void enforceValidConnectionStateTransition() { 347 boolean result = false; 348 if (this == mDisconnected) { 349 result = mPrevState == null || mPrevState == mConnecting 350 || mPrevState == mDisconnecting 351 // TODO: edges to be removed after native stack refactoring 352 // all transitions to disconnected state should go through a pending state 353 // also, states should not go directly from an active audio state to 354 // disconnected state 355 || mPrevState == mConnected || mPrevState == mAudioOn 356 || mPrevState == mAudioConnecting || mPrevState == mAudioDisconnecting; 357 } else if (this == mConnecting) { 358 result = mPrevState == mDisconnected; 359 } else if (this == mDisconnecting) { 360 result = mPrevState == mConnected 361 // TODO: edges to be removed after native stack refactoring 362 // all transitions to disconnecting state should go through connected state 363 || mPrevState == mAudioConnecting || mPrevState == mAudioOn 364 || mPrevState == mAudioDisconnecting; 365 } else if (this == mConnected) { 366 result = mPrevState == mConnecting || mPrevState == mAudioDisconnecting 367 || mPrevState == mDisconnecting || mPrevState == mAudioConnecting 368 // TODO: edges to be removed after native stack refactoring 369 // all transitions to connected state should go through a pending state 370 || mPrevState == mAudioOn || mPrevState == mDisconnected; 371 } else if (this == mAudioConnecting) { 372 result = mPrevState == mConnected; 373 } else if (this == mAudioDisconnecting) { 374 result = mPrevState == mAudioOn; 375 } else if (this == mAudioOn) { 376 result = mPrevState == mAudioConnecting || mPrevState == mAudioDisconnecting 377 // TODO: edges to be removed after native stack refactoring 378 // all transitions to audio connected state should go through a pending 379 // state 380 || mPrevState == mConnected; 381 } 382 if (!result) { 383 throw new IllegalStateException( 384 "Invalid state transition from " + mPrevState + " to " + this 385 + " for device " + mDevice); 386 } 387 } 388 389 void stateLogD(String msg) { 390 log(getName() + ": currentDevice=" + mDevice + ", msg=" + msg); 391 } 392 393 void stateLogW(String msg) { 394 logw(getName() + ": currentDevice=" + mDevice + ", msg=" + msg); 395 } 396 397 void stateLogE(String msg) { 398 loge(getName() + ": currentDevice=" + mDevice + ", msg=" + msg); 399 } 400 401 void stateLogV(String msg) { 402 logv(getName() + ": currentDevice=" + mDevice + ", msg=" + msg); 403 } 404 405 void stateLogI(String msg) { 406 logi(getName() + ": currentDevice=" + mDevice + ", msg=" + msg); 407 } 408 409 void stateLogWtfStack(String msg) { 410 Log.wtfStack(TAG, getName() + ": " + msg); 411 } 412 413 /** 414 * Process connection event 415 * 416 * @param message the current message for the event 417 * @param state connection state to transition to 418 */ 419 public abstract void processConnectionEvent(Message message, int state); 420 421 /** 422 * Get a state value from {@link BluetoothProfile} that represents the connection state of 423 * this headset state 424 * 425 * @return a value in {@link BluetoothProfile#STATE_DISCONNECTED}, 426 * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED}, or 427 * {@link BluetoothProfile#STATE_DISCONNECTING} 428 */ 429 abstract int getConnectionStateInt(); 430 431 /** 432 * Get an audio state value from {@link BluetoothHeadset} 433 * @return a value in {@link BluetoothHeadset#STATE_AUDIO_DISCONNECTED}, 434 * {@link BluetoothHeadset#STATE_AUDIO_CONNECTING}, or 435 * {@link BluetoothHeadset#STATE_AUDIO_CONNECTED} 436 */ 437 abstract int getAudioStateInt(); 438 439 } 440 441 class Disconnected extends HeadsetStateBase { 442 @Override 443 int getConnectionStateInt() { 444 return BluetoothProfile.STATE_DISCONNECTED; 445 } 446 447 @Override 448 int getAudioStateInt() { 449 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 450 } 451 452 @Override 453 public void enter() { 454 super.enter(); 455 mConnectingTimestampMs = Long.MIN_VALUE; 456 mPhonebook.resetAtState(); 457 mSystemInterface.getHeadsetPhoneState().listenForPhoneState(false); 458 mVoiceRecognitionStarted = false; 459 mWaitingForVoiceRecognition = false; 460 mAudioParams.clear(); 461 broadcastStateTransitions(); 462 // Remove the state machine for unbonded devices 463 if (mPrevState != null 464 && mAdapterService.getBondState(mDevice) == BluetoothDevice.BOND_NONE) { 465 getHandler().post(() -> mHeadsetService.removeStateMachine(mDevice)); 466 } 467 } 468 469 @Override 470 public boolean processMessage(Message message) { 471 switch (message.what) { 472 case CONNECT: 473 BluetoothDevice device = (BluetoothDevice) message.obj; 474 stateLogD("Connecting to " + device); 475 if (!mDevice.equals(device)) { 476 stateLogE( 477 "CONNECT failed, device=" + device + ", currentDevice=" + mDevice); 478 break; 479 } 480 if (!mNativeInterface.connectHfp(device)) { 481 stateLogE("CONNECT failed for connectHfp(" + device + ")"); 482 // No state transition is involved, fire broadcast immediately 483 broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED, 484 BluetoothProfile.STATE_DISCONNECTED); 485 break; 486 } 487 transitionTo(mConnecting); 488 break; 489 case DISCONNECT: 490 // ignore 491 break; 492 case CALL_STATE_CHANGED: 493 stateLogD("Ignoring CALL_STATE_CHANGED event"); 494 break; 495 case DEVICE_STATE_CHANGED: 496 stateLogD("Ignoring DEVICE_STATE_CHANGED event"); 497 break; 498 case STACK_EVENT: 499 HeadsetStackEvent event = (HeadsetStackEvent) message.obj; 500 stateLogD("STACK_EVENT: " + event); 501 if (!mDevice.equals(event.device)) { 502 stateLogE("Event device does not match currentDevice[" + mDevice 503 + "], event: " + event); 504 break; 505 } 506 switch (event.type) { 507 case HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: 508 processConnectionEvent(message, event.valueInt); 509 break; 510 default: 511 stateLogE("Unexpected stack event: " + event); 512 break; 513 } 514 break; 515 default: 516 stateLogE("Unexpected msg " + getMessageName(message.what) + ": " + message); 517 return NOT_HANDLED; 518 } 519 return HANDLED; 520 } 521 522 @Override 523 public void processConnectionEvent(Message message, int state) { 524 stateLogD("processConnectionEvent, state=" + state); 525 switch (state) { 526 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED: 527 stateLogW("ignore DISCONNECTED event"); 528 break; 529 // Both events result in Connecting state as SLC establishment is still required 530 case HeadsetHalConstants.CONNECTION_STATE_CONNECTED: 531 case HeadsetHalConstants.CONNECTION_STATE_CONNECTING: 532 if (mHeadsetService.okToAcceptConnection(mDevice)) { 533 stateLogI("accept incoming connection"); 534 transitionTo(mConnecting); 535 } else { 536 stateLogI("rejected incoming HF, priority=" + mHeadsetService.getPriority( 537 mDevice) + " bondState=" + mAdapterService.getBondState(mDevice)); 538 // Reject the connection and stay in Disconnected state itself 539 if (!mNativeInterface.disconnectHfp(mDevice)) { 540 stateLogE("failed to disconnect"); 541 } 542 // Indicate rejection to other components. 543 broadcastConnectionState(mDevice, BluetoothProfile.STATE_DISCONNECTED, 544 BluetoothProfile.STATE_DISCONNECTED); 545 } 546 break; 547 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING: 548 stateLogW("Ignore DISCONNECTING event"); 549 break; 550 default: 551 stateLogE("Incorrect state: " + state); 552 break; 553 } 554 } 555 } 556 557 // Per HFP 1.7.1 spec page 23/144, Pending state needs to handle 558 // AT+BRSF, AT+CIND, AT+CMER, AT+BIND, +CHLD 559 // commands during SLC establishment 560 class Connecting extends HeadsetStateBase { 561 @Override 562 int getConnectionStateInt() { 563 return BluetoothProfile.STATE_CONNECTING; 564 } 565 566 @Override 567 int getAudioStateInt() { 568 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 569 } 570 571 @Override 572 public void enter() { 573 super.enter(); 574 mConnectingTimestampMs = SystemClock.uptimeMillis(); 575 sendMessageDelayed(CONNECT_TIMEOUT, mDevice, sConnectTimeoutMs); 576 broadcastStateTransitions(); 577 } 578 579 @Override 580 public boolean processMessage(Message message) { 581 switch (message.what) { 582 case CONNECT: 583 case CONNECT_AUDIO: 584 case DISCONNECT: 585 deferMessage(message); 586 break; 587 case CONNECT_TIMEOUT: { 588 // We timed out trying to connect, transition to Disconnected state 589 BluetoothDevice device = (BluetoothDevice) message.obj; 590 if (!mDevice.equals(device)) { 591 stateLogE("Unknown device timeout " + device); 592 break; 593 } 594 stateLogW("CONNECT_TIMEOUT"); 595 transitionTo(mDisconnected); 596 break; 597 } 598 case CALL_STATE_CHANGED: 599 stateLogD("ignoring CALL_STATE_CHANGED event"); 600 break; 601 case DEVICE_STATE_CHANGED: 602 stateLogD("ignoring DEVICE_STATE_CHANGED event"); 603 break; 604 case STACK_EVENT: 605 HeadsetStackEvent event = (HeadsetStackEvent) message.obj; 606 stateLogD("STACK_EVENT: " + event); 607 if (!mDevice.equals(event.device)) { 608 stateLogE("Event device does not match currentDevice[" + mDevice 609 + "], event: " + event); 610 break; 611 } 612 switch (event.type) { 613 case HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: 614 processConnectionEvent(message, event.valueInt); 615 break; 616 case HeadsetStackEvent.EVENT_TYPE_AT_CHLD: 617 processAtChld(event.valueInt, event.device); 618 break; 619 case HeadsetStackEvent.EVENT_TYPE_AT_CIND: 620 processAtCind(event.device); 621 break; 622 case HeadsetStackEvent.EVENT_TYPE_WBS: 623 processWBSEvent(event.valueInt); 624 break; 625 case HeadsetStackEvent.EVENT_TYPE_BIND: 626 processAtBind(event.valueString, event.device); 627 break; 628 // Unexpected AT commands, we only handle them for comparability reasons 629 case HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED: 630 stateLogW("Unexpected VR event, device=" + event.device + ", state=" 631 + event.valueInt); 632 processVrEvent(event.valueInt, event.device); 633 break; 634 case HeadsetStackEvent.EVENT_TYPE_DIAL_CALL: 635 stateLogW("Unexpected dial event, device=" + event.device); 636 processDialCall(event.valueString, event.device); 637 break; 638 case HeadsetStackEvent.EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST: 639 stateLogW("Unexpected subscriber number event for" + event.device 640 + ", state=" + event.valueInt); 641 processSubscriberNumberRequest(event.device); 642 break; 643 case HeadsetStackEvent.EVENT_TYPE_AT_COPS: 644 stateLogW("Unexpected COPS event for " + event.device); 645 processAtCops(event.device); 646 break; 647 case HeadsetStackEvent.EVENT_TYPE_AT_CLCC: 648 Log.w(TAG, "Connecting: Unexpected CLCC event for" + event.device); 649 processAtClcc(event.device); 650 break; 651 case HeadsetStackEvent.EVENT_TYPE_UNKNOWN_AT: 652 stateLogW("Unexpected unknown AT event for" + event.device + ", cmd=" 653 + event.valueString); 654 processUnknownAt(event.valueString, event.device); 655 break; 656 case HeadsetStackEvent.EVENT_TYPE_KEY_PRESSED: 657 stateLogW("Unexpected key-press event for " + event.device); 658 processKeyPressed(event.device); 659 break; 660 case HeadsetStackEvent.EVENT_TYPE_BIEV: 661 stateLogW("Unexpected BIEV event for " + event.device + ", indId=" 662 + event.valueInt + ", indVal=" + event.valueInt2); 663 processAtBiev(event.valueInt, event.valueInt2, event.device); 664 break; 665 case HeadsetStackEvent.EVENT_TYPE_VOLUME_CHANGED: 666 stateLogW("Unexpected volume event for " + event.device); 667 processVolumeEvent(event.valueInt, event.valueInt2); 668 break; 669 case HeadsetStackEvent.EVENT_TYPE_ANSWER_CALL: 670 stateLogW("Unexpected answer event for " + event.device); 671 mSystemInterface.answerCall(event.device); 672 break; 673 case HeadsetStackEvent.EVENT_TYPE_HANGUP_CALL: 674 stateLogW("Unexpected hangup event for " + event.device); 675 mSystemInterface.hangupCall(event.device, isVirtualCallInProgress()); 676 break; 677 default: 678 stateLogE("Unexpected event: " + event); 679 break; 680 } 681 break; 682 default: 683 stateLogE("Unexpected msg " + getMessageName(message.what) + ": " + message); 684 return NOT_HANDLED; 685 } 686 return HANDLED; 687 } 688 689 @Override 690 public void processConnectionEvent(Message message, int state) { 691 stateLogD("processConnectionEvent, state=" + state); 692 switch (state) { 693 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED: 694 stateLogW("Disconnected"); 695 transitionTo(mDisconnected); 696 break; 697 case HeadsetHalConstants.CONNECTION_STATE_CONNECTED: 698 stateLogD("RFCOMM connected"); 699 break; 700 case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED: 701 stateLogD("SLC connected"); 702 transitionTo(mConnected); 703 break; 704 case HeadsetHalConstants.CONNECTION_STATE_CONNECTING: 705 // Ignored 706 break; 707 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING: 708 stateLogW("Disconnecting"); 709 break; 710 default: 711 stateLogE("Incorrect state " + state); 712 break; 713 } 714 } 715 716 @Override 717 public void exit() { 718 removeMessages(CONNECT_TIMEOUT); 719 super.exit(); 720 } 721 } 722 723 class Disconnecting extends HeadsetStateBase { 724 @Override 725 int getConnectionStateInt() { 726 return BluetoothProfile.STATE_DISCONNECTING; 727 } 728 729 @Override 730 int getAudioStateInt() { 731 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 732 } 733 734 @Override 735 public void enter() { 736 super.enter(); 737 sendMessageDelayed(CONNECT_TIMEOUT, mDevice, sConnectTimeoutMs); 738 broadcastStateTransitions(); 739 } 740 741 @Override 742 public boolean processMessage(Message message) { 743 switch (message.what) { 744 case CONNECT: 745 case CONNECT_AUDIO: 746 case DISCONNECT: 747 deferMessage(message); 748 break; 749 case CONNECT_TIMEOUT: { 750 BluetoothDevice device = (BluetoothDevice) message.obj; 751 if (!mDevice.equals(device)) { 752 stateLogE("Unknown device timeout " + device); 753 break; 754 } 755 stateLogE("timeout"); 756 transitionTo(mDisconnected); 757 break; 758 } 759 case STACK_EVENT: 760 HeadsetStackEvent event = (HeadsetStackEvent) message.obj; 761 stateLogD("STACK_EVENT: " + event); 762 if (!mDevice.equals(event.device)) { 763 stateLogE("Event device does not match currentDevice[" + mDevice 764 + "], event: " + event); 765 break; 766 } 767 switch (event.type) { 768 case HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: 769 processConnectionEvent(message, event.valueInt); 770 break; 771 default: 772 stateLogE("Unexpected event: " + event); 773 break; 774 } 775 break; 776 default: 777 stateLogE("Unexpected msg " + getMessageName(message.what) + ": " + message); 778 return NOT_HANDLED; 779 } 780 return HANDLED; 781 } 782 783 // in Disconnecting state 784 @Override 785 public void processConnectionEvent(Message message, int state) { 786 switch (state) { 787 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED: 788 stateLogD("processConnectionEvent: Disconnected"); 789 transitionTo(mDisconnected); 790 break; 791 case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED: 792 stateLogD("processConnectionEvent: Connected"); 793 transitionTo(mConnected); 794 break; 795 default: 796 stateLogE("processConnectionEvent: Bad state: " + state); 797 break; 798 } 799 } 800 801 @Override 802 public void exit() { 803 removeMessages(CONNECT_TIMEOUT); 804 super.exit(); 805 } 806 } 807 808 /** 809 * Base class for Connected, AudioConnecting, AudioOn, AudioDisconnecting states 810 */ 811 private abstract class ConnectedBase extends HeadsetStateBase { 812 @Override 813 int getConnectionStateInt() { 814 return BluetoothProfile.STATE_CONNECTED; 815 } 816 817 /** 818 * Handle common messages in connected states. However, state specific messages must be 819 * handled individually. 820 * 821 * @param message Incoming message to handle 822 * @return True if handled successfully, False otherwise 823 */ 824 @Override 825 public boolean processMessage(Message message) { 826 switch (message.what) { 827 case CONNECT: 828 case DISCONNECT: 829 case CONNECT_AUDIO: 830 case DISCONNECT_AUDIO: 831 case CONNECT_TIMEOUT: 832 throw new IllegalStateException( 833 "Illegal message in generic handler: " + message); 834 case VOICE_RECOGNITION_START: { 835 BluetoothDevice device = (BluetoothDevice) message.obj; 836 if (!mDevice.equals(device)) { 837 stateLogW("VOICE_RECOGNITION_START failed " + device 838 + " is not currentDevice"); 839 break; 840 } 841 processLocalVrEvent(HeadsetHalConstants.VR_STATE_STARTED); 842 break; 843 } 844 case VOICE_RECOGNITION_STOP: { 845 BluetoothDevice device = (BluetoothDevice) message.obj; 846 if (!mDevice.equals(device)) { 847 stateLogW("VOICE_RECOGNITION_STOP failed " + device 848 + " is not currentDevice"); 849 break; 850 } 851 processLocalVrEvent(HeadsetHalConstants.VR_STATE_STOPPED); 852 break; 853 } 854 case CALL_STATE_CHANGED: 855 processCallState((HeadsetCallState) message.obj, message.arg1 == 1); 856 break; 857 case DEVICE_STATE_CHANGED: 858 mNativeInterface.notifyDeviceStatus(mDevice, (HeadsetDeviceState) message.obj); 859 break; 860 case SEND_CCLC_RESPONSE: 861 processSendClccResponse((HeadsetClccResponse) message.obj); 862 break; 863 case CLCC_RSP_TIMEOUT: { 864 BluetoothDevice device = (BluetoothDevice) message.obj; 865 if (!mDevice.equals(device)) { 866 stateLogW("CLCC_RSP_TIMEOUT failed " + device + " is not currentDevice"); 867 break; 868 } 869 mNativeInterface.clccResponse(device, 0, 0, 0, 0, false, "", 0); 870 } 871 break; 872 case SEND_VENDOR_SPECIFIC_RESULT_CODE: 873 processSendVendorSpecificResultCode( 874 (HeadsetVendorSpecificResultCode) message.obj); 875 break; 876 case SEND_BSIR: 877 mNativeInterface.sendBsir(mDevice, message.arg1 == 1); 878 break; 879 case DIALING_OUT_TIMEOUT: { 880 BluetoothDevice device = (BluetoothDevice) message.obj; 881 if (!mDevice.equals(device)) { 882 stateLogW("DIALING_OUT_TIMEOUT failed " + device + " is not currentDevice"); 883 break; 884 } 885 if (mDialingOut) { 886 mDialingOut = false; 887 mNativeInterface.atResponseCode(device, 888 HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 889 } 890 } 891 break; 892 case VIRTUAL_CALL_START: { 893 BluetoothDevice device = (BluetoothDevice) message.obj; 894 if (!mDevice.equals(device)) { 895 stateLogW("VIRTUAL_CALL_START failed " + device + " is not currentDevice"); 896 break; 897 } 898 initiateScoUsingVirtualVoiceCall(); 899 break; 900 } 901 case VIRTUAL_CALL_STOP: { 902 BluetoothDevice device = (BluetoothDevice) message.obj; 903 if (!mDevice.equals(device)) { 904 stateLogW("VIRTUAL_CALL_STOP failed " + device + " is not currentDevice"); 905 break; 906 } 907 terminateScoUsingVirtualVoiceCall(); 908 break; 909 } 910 case START_VR_TIMEOUT: { 911 BluetoothDevice device = (BluetoothDevice) message.obj; 912 if (!mDevice.equals(device)) { 913 stateLogW("START_VR_TIMEOUT failed " + device + " is not currentDevice"); 914 break; 915 } 916 if (mWaitingForVoiceRecognition) { 917 mWaitingForVoiceRecognition = false; 918 stateLogE("Timeout waiting for voice recognition to start"); 919 mNativeInterface.atResponseCode(device, 920 HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 921 } 922 } 923 break; 924 case INTENT_CONNECTION_ACCESS_REPLY: 925 handleAccessPermissionResult((Intent) message.obj); 926 break; 927 case STACK_EVENT: 928 HeadsetStackEvent event = (HeadsetStackEvent) message.obj; 929 stateLogD("STACK_EVENT: " + event); 930 if (!mDevice.equals(event.device)) { 931 stateLogE("Event device does not match currentDevice[" + mDevice 932 + "], event: " + event); 933 break; 934 } 935 switch (event.type) { 936 case HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: 937 processConnectionEvent(message, event.valueInt); 938 break; 939 case HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED: 940 processAudioEvent(event.valueInt); 941 break; 942 case HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED: 943 processVrEvent(event.valueInt, event.device); 944 break; 945 case HeadsetStackEvent.EVENT_TYPE_ANSWER_CALL: 946 mSystemInterface.answerCall(event.device); 947 break; 948 case HeadsetStackEvent.EVENT_TYPE_HANGUP_CALL: 949 mSystemInterface.hangupCall(event.device, mVirtualCallStarted); 950 break; 951 case HeadsetStackEvent.EVENT_TYPE_VOLUME_CHANGED: 952 processVolumeEvent(event.valueInt, event.valueInt2); 953 break; 954 case HeadsetStackEvent.EVENT_TYPE_DIAL_CALL: 955 processDialCall(event.valueString, event.device); 956 break; 957 case HeadsetStackEvent.EVENT_TYPE_SEND_DTMF: 958 mSystemInterface.sendDtmf(event.valueInt, event.device); 959 break; 960 case HeadsetStackEvent.EVENT_TYPE_NOICE_REDUCTION: 961 processNoiseReductionEvent(event.valueInt == 1); 962 break; 963 case HeadsetStackEvent.EVENT_TYPE_WBS: 964 processWBSEvent(event.valueInt); 965 break; 966 case HeadsetStackEvent.EVENT_TYPE_AT_CHLD: 967 processAtChld(event.valueInt, event.device); 968 break; 969 case HeadsetStackEvent.EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST: 970 processSubscriberNumberRequest(event.device); 971 break; 972 case HeadsetStackEvent.EVENT_TYPE_AT_CIND: 973 processAtCind(event.device); 974 break; 975 case HeadsetStackEvent.EVENT_TYPE_AT_COPS: 976 processAtCops(event.device); 977 break; 978 case HeadsetStackEvent.EVENT_TYPE_AT_CLCC: 979 processAtClcc(event.device); 980 break; 981 case HeadsetStackEvent.EVENT_TYPE_UNKNOWN_AT: 982 processUnknownAt(event.valueString, event.device); 983 break; 984 case HeadsetStackEvent.EVENT_TYPE_KEY_PRESSED: 985 processKeyPressed(event.device); 986 break; 987 case HeadsetStackEvent.EVENT_TYPE_BIND: 988 processAtBind(event.valueString, event.device); 989 break; 990 case HeadsetStackEvent.EVENT_TYPE_BIEV: 991 processAtBiev(event.valueInt, event.valueInt2, event.device); 992 break; 993 default: 994 stateLogE("Unknown stack event: " + event); 995 break; 996 } 997 break; 998 default: 999 stateLogE("Unexpected msg " + getMessageName(message.what) + ": " + message); 1000 return NOT_HANDLED; 1001 } 1002 return HANDLED; 1003 } 1004 1005 @Override 1006 public void processConnectionEvent(Message message, int state) { 1007 stateLogD("processConnectionEvent, state=" + state); 1008 switch (state) { 1009 case HeadsetHalConstants.CONNECTION_STATE_CONNECTED: 1010 stateLogE("processConnectionEvent: RFCOMM connected again, shouldn't happen"); 1011 break; 1012 case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED: 1013 stateLogE("processConnectionEvent: SLC connected again, shouldn't happen"); 1014 break; 1015 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING: 1016 stateLogI("processConnectionEvent: Disconnecting"); 1017 transitionTo(mDisconnecting); 1018 break; 1019 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED: 1020 stateLogI("processConnectionEvent: Disconnected"); 1021 transitionTo(mDisconnected); 1022 break; 1023 default: 1024 stateLogE("processConnectionEvent: bad state: " + state); 1025 break; 1026 } 1027 } 1028 1029 /** 1030 * Each state should handle audio events differently 1031 * 1032 * @param state audio state 1033 */ 1034 public abstract void processAudioEvent(int state); 1035 } 1036 1037 class Connected extends ConnectedBase { 1038 @Override 1039 int getAudioStateInt() { 1040 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 1041 } 1042 1043 @Override 1044 public void enter() { 1045 super.enter(); 1046 if (mConnectingTimestampMs == Long.MIN_VALUE) { 1047 mConnectingTimestampMs = SystemClock.uptimeMillis(); 1048 } 1049 // start phone state listener here so that the CIND response as part of SLC can be 1050 // responded to, correctly. 1051 // listenForPhoneState(boolean) internally handles multiple calls to start listen 1052 mSystemInterface.getHeadsetPhoneState().listenForPhoneState(true); 1053 if (mPrevState == mConnecting) { 1054 // Reset NREC on connect event. Headset will override later 1055 processNoiseReductionEvent(true); 1056 // Query phone state for initial setup 1057 mSystemInterface.queryPhoneState(); 1058 // Remove pending connection attempts that were deferred during the pending 1059 // state. This is to prevent auto connect attempts from disconnecting 1060 // devices that previously successfully connected. 1061 removeDeferredMessages(CONNECT); 1062 } 1063 broadcastStateTransitions(); 1064 } 1065 1066 @Override 1067 public boolean processMessage(Message message) { 1068 switch (message.what) { 1069 case CONNECT: { 1070 BluetoothDevice device = (BluetoothDevice) message.obj; 1071 stateLogW("CONNECT, ignored, device=" + device + ", currentDevice" + mDevice); 1072 break; 1073 } 1074 case DISCONNECT: { 1075 BluetoothDevice device = (BluetoothDevice) message.obj; 1076 stateLogD("DISCONNECT from device=" + device); 1077 if (!mDevice.equals(device)) { 1078 stateLogW("DISCONNECT, device " + device + " not connected"); 1079 break; 1080 } 1081 if (!mNativeInterface.disconnectHfp(device)) { 1082 // broadcast immediately as no state transition is involved 1083 stateLogE("DISCONNECT from " + device + " failed"); 1084 broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED, 1085 BluetoothProfile.STATE_CONNECTED); 1086 break; 1087 } 1088 transitionTo(mDisconnecting); 1089 } 1090 break; 1091 case CONNECT_AUDIO: 1092 stateLogD("CONNECT_AUDIO, device=" + mDevice); 1093 if (!isScoAcceptable()) { 1094 stateLogW("CONNECT_AUDIO No Active/Held call, no call setup, and no " 1095 + "in-band ringing, not allowing SCO, device=" + mDevice); 1096 break; 1097 } 1098 if (!mNativeInterface.connectAudio(mDevice)) { 1099 stateLogE("Failed to connect SCO audio for " + mDevice); 1100 // No state change involved, fire broadcast immediately 1101 broadcastAudioState(mDevice, BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 1102 BluetoothHeadset.STATE_AUDIO_DISCONNECTED); 1103 break; 1104 } 1105 transitionTo(mAudioConnecting); 1106 break; 1107 case DISCONNECT_AUDIO: 1108 stateLogD("ignore DISCONNECT_AUDIO, device=" + mDevice); 1109 // ignore 1110 break; 1111 default: 1112 return super.processMessage(message); 1113 } 1114 return HANDLED; 1115 } 1116 1117 @Override 1118 public void processAudioEvent(int state) { 1119 stateLogD("processAudioEvent, state=" + state); 1120 switch (state) { 1121 case HeadsetHalConstants.AUDIO_STATE_CONNECTED: 1122 if (!isScoAcceptable()) { 1123 stateLogW("processAudioEvent: reject incoming audio connection"); 1124 if (!mNativeInterface.disconnectAudio(mDevice)) { 1125 stateLogE("processAudioEvent: failed to disconnect audio"); 1126 } 1127 break; 1128 } 1129 stateLogI("processAudioEvent: audio connected"); 1130 transitionTo(mAudioOn); 1131 break; 1132 case HeadsetHalConstants.AUDIO_STATE_CONNECTING: 1133 if (!isScoAcceptable()) { 1134 stateLogW("processAudioEvent: reject incoming pending audio connection"); 1135 if (!mNativeInterface.disconnectAudio(mDevice)) { 1136 stateLogE("processAudioEvent: failed to disconnect pending audio"); 1137 } 1138 break; 1139 } 1140 stateLogI("processAudioEvent: audio connecting"); 1141 transitionTo(mAudioConnecting); 1142 break; 1143 case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED: 1144 case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING: 1145 // ignore 1146 break; 1147 default: 1148 stateLogE("processAudioEvent: bad state: " + state); 1149 break; 1150 } 1151 } 1152 } 1153 1154 class AudioConnecting extends ConnectedBase { 1155 @Override 1156 int getAudioStateInt() { 1157 return BluetoothHeadset.STATE_AUDIO_CONNECTING; 1158 } 1159 1160 @Override 1161 public void enter() { 1162 super.enter(); 1163 sendMessageDelayed(CONNECT_TIMEOUT, mDevice, sConnectTimeoutMs); 1164 broadcastStateTransitions(); 1165 } 1166 1167 @Override 1168 public boolean processMessage(Message message) { 1169 switch (message.what) { 1170 case CONNECT: 1171 case DISCONNECT: 1172 case CONNECT_AUDIO: 1173 case DISCONNECT_AUDIO: 1174 deferMessage(message); 1175 break; 1176 case CONNECT_TIMEOUT: { 1177 BluetoothDevice device = (BluetoothDevice) message.obj; 1178 if (!mDevice.equals(device)) { 1179 stateLogW("CONNECT_TIMEOUT for unknown device " + device); 1180 break; 1181 } 1182 stateLogW("CONNECT_TIMEOUT"); 1183 transitionTo(mConnected); 1184 break; 1185 } 1186 default: 1187 return super.processMessage(message); 1188 } 1189 return HANDLED; 1190 } 1191 1192 @Override 1193 public void processAudioEvent(int state) { 1194 switch (state) { 1195 case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED: 1196 stateLogW("processAudioEvent: audio connection failed"); 1197 transitionTo(mConnected); 1198 break; 1199 case HeadsetHalConstants.AUDIO_STATE_CONNECTING: 1200 // ignore, already in audio connecting state 1201 break; 1202 case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING: 1203 // ignore, there is no BluetoothHeadset.STATE_AUDIO_DISCONNECTING 1204 break; 1205 case HeadsetHalConstants.AUDIO_STATE_CONNECTED: 1206 stateLogI("processAudioEvent: audio connected"); 1207 transitionTo(mAudioOn); 1208 break; 1209 default: 1210 stateLogE("processAudioEvent: bad state: " + state); 1211 break; 1212 } 1213 } 1214 1215 @Override 1216 public void exit() { 1217 removeMessages(CONNECT_TIMEOUT); 1218 super.exit(); 1219 } 1220 } 1221 1222 class AudioOn extends ConnectedBase { 1223 @Override 1224 int getAudioStateInt() { 1225 return BluetoothHeadset.STATE_AUDIO_CONNECTED; 1226 } 1227 1228 @Override 1229 public void enter() { 1230 super.enter(); 1231 removeDeferredMessages(CONNECT_AUDIO); 1232 // Set active device to current active SCO device when the current active device 1233 // is different from mCurrentDevice. This is to accommodate active device state 1234 // mis-match between native and Java. 1235 if (!mDevice.equals(mHeadsetService.getActiveDevice())) { 1236 mHeadsetService.setActiveDevice(mDevice); 1237 } 1238 setAudioParameters(); 1239 mSystemInterface.getAudioManager().setBluetoothScoOn(true); 1240 broadcastStateTransitions(); 1241 } 1242 1243 @Override 1244 public boolean processMessage(Message message) { 1245 switch (message.what) { 1246 case CONNECT: { 1247 BluetoothDevice device = (BluetoothDevice) message.obj; 1248 stateLogW("CONNECT, ignored, device=" + device + ", currentDevice" + mDevice); 1249 break; 1250 } 1251 case DISCONNECT: { 1252 BluetoothDevice device = (BluetoothDevice) message.obj; 1253 stateLogD("DISCONNECT, device=" + device); 1254 if (!mDevice.equals(device)) { 1255 stateLogW("DISCONNECT, device " + device + " not connected"); 1256 break; 1257 } 1258 // Disconnect BT SCO first 1259 if (!mNativeInterface.disconnectAudio(mDevice)) { 1260 stateLogW("DISCONNECT failed, device=" + mDevice); 1261 // if disconnect BT SCO failed, transition to mConnected state to force 1262 // disconnect device 1263 } 1264 deferMessage(obtainMessage(DISCONNECT, mDevice)); 1265 transitionTo(mAudioDisconnecting); 1266 break; 1267 } 1268 case CONNECT_AUDIO: { 1269 BluetoothDevice device = (BluetoothDevice) message.obj; 1270 if (!mDevice.equals(device)) { 1271 stateLogW("CONNECT_AUDIO device is not connected " + device); 1272 break; 1273 } 1274 stateLogW("CONNECT_AUDIO device auido is already connected " + device); 1275 break; 1276 } 1277 case DISCONNECT_AUDIO: { 1278 BluetoothDevice device = (BluetoothDevice) message.obj; 1279 if (!mDevice.equals(device)) { 1280 stateLogW("DISCONNECT_AUDIO, failed, device=" + device + ", currentDevice=" 1281 + mDevice); 1282 break; 1283 } 1284 if (mNativeInterface.disconnectAudio(mDevice)) { 1285 stateLogD("DISCONNECT_AUDIO, device=" + mDevice); 1286 transitionTo(mAudioDisconnecting); 1287 } else { 1288 stateLogW("DISCONNECT_AUDIO failed, device=" + mDevice); 1289 } 1290 break; 1291 } 1292 case INTENT_SCO_VOLUME_CHANGED: 1293 processIntentScoVolume((Intent) message.obj, mDevice); 1294 break; 1295 case STACK_EVENT: 1296 HeadsetStackEvent event = (HeadsetStackEvent) message.obj; 1297 stateLogD("STACK_EVENT: " + event); 1298 if (!mDevice.equals(event.device)) { 1299 stateLogE("Event device does not match currentDevice[" + mDevice 1300 + "], event: " + event); 1301 break; 1302 } 1303 switch (event.type) { 1304 case HeadsetStackEvent.EVENT_TYPE_WBS: 1305 stateLogE("Cannot change WBS state when audio is connected: " + event); 1306 break; 1307 default: 1308 super.processMessage(message); 1309 break; 1310 } 1311 break; 1312 default: 1313 return super.processMessage(message); 1314 } 1315 return HANDLED; 1316 } 1317 1318 @Override 1319 public void processAudioEvent(int state) { 1320 switch (state) { 1321 case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED: 1322 stateLogI("processAudioEvent: audio disconnected by remote"); 1323 transitionTo(mConnected); 1324 break; 1325 case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING: 1326 stateLogI("processAudioEvent: audio being disconnected by remote"); 1327 transitionTo(mAudioDisconnecting); 1328 break; 1329 default: 1330 stateLogE("processAudioEvent: bad state: " + state); 1331 break; 1332 } 1333 } 1334 1335 private void processIntentScoVolume(Intent intent, BluetoothDevice device) { 1336 int volumeValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0); 1337 if (mSpeakerVolume != volumeValue) { 1338 mSpeakerVolume = volumeValue; 1339 mNativeInterface.setVolume(device, HeadsetHalConstants.VOLUME_TYPE_SPK, 1340 mSpeakerVolume); 1341 } 1342 } 1343 1344 @Override 1345 public void exit() { 1346 mSystemInterface.getAudioManager().setBluetoothScoOn(false); 1347 super.exit(); 1348 } 1349 } 1350 1351 class AudioDisconnecting extends ConnectedBase { 1352 @Override 1353 int getAudioStateInt() { 1354 // TODO: need BluetoothHeadset.STATE_AUDIO_DISCONNECTING 1355 return BluetoothHeadset.STATE_AUDIO_CONNECTED; 1356 } 1357 1358 @Override 1359 public void enter() { 1360 super.enter(); 1361 sendMessageDelayed(CONNECT_TIMEOUT, mDevice, sConnectTimeoutMs); 1362 broadcastStateTransitions(); 1363 } 1364 1365 @Override 1366 public boolean processMessage(Message message) { 1367 switch (message.what) { 1368 case CONNECT: 1369 case DISCONNECT: 1370 case CONNECT_AUDIO: 1371 case DISCONNECT_AUDIO: 1372 deferMessage(message); 1373 break; 1374 case CONNECT_TIMEOUT: { 1375 BluetoothDevice device = (BluetoothDevice) message.obj; 1376 if (!mDevice.equals(device)) { 1377 stateLogW("CONNECT_TIMEOUT for unknown device " + device); 1378 break; 1379 } 1380 stateLogW("CONNECT_TIMEOUT"); 1381 transitionTo(mConnected); 1382 break; 1383 } 1384 default: 1385 return super.processMessage(message); 1386 } 1387 return HANDLED; 1388 } 1389 1390 @Override 1391 public void processAudioEvent(int state) { 1392 switch (state) { 1393 case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED: 1394 stateLogI("processAudioEvent: audio disconnected"); 1395 transitionTo(mConnected); 1396 break; 1397 case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING: 1398 // ignore 1399 break; 1400 case HeadsetHalConstants.AUDIO_STATE_CONNECTED: 1401 stateLogW("processAudioEvent: audio disconnection failed"); 1402 transitionTo(mAudioOn); 1403 break; 1404 case HeadsetHalConstants.AUDIO_STATE_CONNECTING: 1405 // ignore, see if it goes into connected state, otherwise, timeout 1406 break; 1407 default: 1408 stateLogE("processAudioEvent: bad state: " + state); 1409 break; 1410 } 1411 } 1412 1413 @Override 1414 public void exit() { 1415 removeMessages(CONNECT_TIMEOUT); 1416 super.exit(); 1417 } 1418 } 1419 1420 /** 1421 * Get the underlying device tracked by this state machine 1422 * 1423 * @return device in focus 1424 */ 1425 @VisibleForTesting 1426 public synchronized BluetoothDevice getDevice() { 1427 return mDevice; 1428 } 1429 1430 /** 1431 * Get the current connection state of this state machine 1432 * 1433 * @return current connection state, one of {@link BluetoothProfile#STATE_DISCONNECTED}, 1434 * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED}, or 1435 * {@link BluetoothProfile#STATE_DISCONNECTING} 1436 */ 1437 @VisibleForTesting 1438 public synchronized int getConnectionState() { 1439 HeadsetStateBase state = (HeadsetStateBase) getCurrentState(); 1440 if (state == null) { 1441 return BluetoothHeadset.STATE_DISCONNECTED; 1442 } 1443 return state.getConnectionStateInt(); 1444 } 1445 1446 /** 1447 * Get the current audio state of this state machine 1448 * 1449 * @return current audio state, one of {@link BluetoothHeadset#STATE_AUDIO_DISCONNECTED}, 1450 * {@link BluetoothHeadset#STATE_AUDIO_CONNECTING}, or 1451 * {@link BluetoothHeadset#STATE_AUDIO_CONNECTED} 1452 */ 1453 public synchronized int getAudioState() { 1454 HeadsetStateBase state = (HeadsetStateBase) getCurrentState(); 1455 if (state == null) { 1456 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 1457 } 1458 return state.getAudioStateInt(); 1459 } 1460 1461 private void processVrEvent(int state, BluetoothDevice device) { 1462 Log.d(TAG, "processVrEvent: state=" + state + " mVoiceRecognitionStarted: " 1463 + mVoiceRecognitionStarted + " mWaitingforVoiceRecognition: " 1464 + mWaitingForVoiceRecognition + " isInCall: " + mSystemInterface.isInCall()); 1465 if (state == HeadsetHalConstants.VR_STATE_STARTED) { 1466 if (!isVirtualCallInProgress() && !mSystemInterface.isInCall()) { 1467 IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface( 1468 ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); 1469 if (dic != null) { 1470 try { 1471 dic.exitIdle("voice-command"); 1472 } catch (RemoteException e) { 1473 } 1474 } 1475 try { 1476 mHeadsetService.startActivity(VOICE_COMMAND_INTENT); 1477 } catch (ActivityNotFoundException e) { 1478 mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 1479 0); 1480 return; 1481 } 1482 expectVoiceRecognition(device); 1483 } else { 1484 // send error response if call is ongoing 1485 mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1486 } 1487 } else if (state == HeadsetHalConstants.VR_STATE_STOPPED) { 1488 if (mVoiceRecognitionStarted || mWaitingForVoiceRecognition) { 1489 mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0); 1490 mVoiceRecognitionStarted = false; 1491 mWaitingForVoiceRecognition = false; 1492 if (!mSystemInterface.isInCall() && (getAudioState() 1493 != BluetoothHeadset.STATE_AUDIO_DISCONNECTED)) { 1494 mNativeInterface.disconnectAudio(mDevice); 1495 mSystemInterface.getAudioManager().setParameters("A2dpSuspended=false"); 1496 } 1497 } else { 1498 mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1499 } 1500 } else { 1501 Log.e(TAG, "Bad Voice Recognition state: " + state); 1502 } 1503 } 1504 1505 private void processLocalVrEvent(int state) { 1506 if (state == HeadsetHalConstants.VR_STATE_STARTED) { 1507 boolean needAudio = true; 1508 if (mVoiceRecognitionStarted || mSystemInterface.isInCall()) { 1509 Log.e(TAG, "Voice recognition started when call is active. isInCall:" 1510 + mSystemInterface.isInCall() + " mVoiceRecognitionStarted: " 1511 + mVoiceRecognitionStarted); 1512 return; 1513 } 1514 mVoiceRecognitionStarted = true; 1515 1516 if (mWaitingForVoiceRecognition) { 1517 if (!hasMessages(START_VR_TIMEOUT)) { 1518 return; 1519 } 1520 Log.d(TAG, "Voice recognition started successfully"); 1521 mWaitingForVoiceRecognition = false; 1522 mNativeInterface.atResponseCode(mDevice, HeadsetHalConstants.AT_RESPONSE_OK, 0); 1523 removeMessages(START_VR_TIMEOUT); 1524 } else { 1525 Log.d(TAG, "Voice recognition started locally"); 1526 needAudio = mNativeInterface.startVoiceRecognition(mDevice); 1527 } 1528 1529 if (needAudio && getAudioState() == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { 1530 Log.d(TAG, "Initiating audio connection for Voice Recognition"); 1531 // At this stage, we need to be sure that AVDTP is not streaming. This is needed 1532 // to be compliant with the AV+HFP Whitepaper as we cannot have A2DP in 1533 // streaming state while a SCO connection is established. 1534 // This is needed for VoiceDial scenario alone and not for 1535 // incoming call/outgoing call scenarios as the phone enters MODE_RINGTONE 1536 // or MODE_IN_CALL which shall automatically suspend the AVDTP stream if needed. 1537 // Whereas for VoiceDial we want to activate the SCO connection but we are still 1538 // in MODE_NORMAL and hence the need to explicitly suspend the A2DP stream 1539 mSystemInterface.getAudioManager().setParameters("A2dpSuspended=true"); 1540 mNativeInterface.connectAudio(mDevice); 1541 } 1542 1543 if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) { 1544 mSystemInterface.getVoiceRecognitionWakeLock().release(); 1545 } 1546 } else { 1547 Log.d(TAG, "Voice Recognition stopped. mVoiceRecognitionStarted: " 1548 + mVoiceRecognitionStarted + " mWaitingForVoiceRecognition: " 1549 + mWaitingForVoiceRecognition); 1550 if (mVoiceRecognitionStarted || mWaitingForVoiceRecognition) { 1551 mVoiceRecognitionStarted = false; 1552 mWaitingForVoiceRecognition = false; 1553 1554 if (mNativeInterface.stopVoiceRecognition(mDevice) && !mSystemInterface.isInCall() 1555 && getAudioState() != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { 1556 mNativeInterface.disconnectAudio(mDevice); 1557 mSystemInterface.getAudioManager().setParameters("A2dpSuspended=false"); 1558 } 1559 } 1560 } 1561 } 1562 1563 private synchronized void expectVoiceRecognition(BluetoothDevice device) { 1564 mWaitingForVoiceRecognition = true; 1565 mHeadsetService.setActiveDevice(device); 1566 sendMessageDelayed(START_VR_TIMEOUT, device, START_VR_TIMEOUT_MS); 1567 if (!mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) { 1568 mSystemInterface.getVoiceRecognitionWakeLock().acquire(START_VR_TIMEOUT_MS); 1569 } 1570 } 1571 1572 public long getConnectingTimestampMs() { 1573 return mConnectingTimestampMs; 1574 } 1575 1576 /* 1577 * Put the AT command, company ID, arguments, and device in an Intent and broadcast it. 1578 */ 1579 private void broadcastVendorSpecificEventIntent(String command, int companyId, int commandType, 1580 Object[] arguments, BluetoothDevice device) { 1581 log("broadcastVendorSpecificEventIntent(" + command + ")"); 1582 Intent intent = new Intent(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT); 1583 intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD, command); 1584 intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE, commandType); 1585 // assert: all elements of args are Serializable 1586 intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS, arguments); 1587 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 1588 1589 intent.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "." 1590 + Integer.toString(companyId)); 1591 1592 mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL, HeadsetService.BLUETOOTH_PERM); 1593 } 1594 1595 private void setAudioParameters() { 1596 String keyValuePairs = String.join(";", new String[]{ 1597 HEADSET_NAME + "=" + getCurrentDeviceName(), 1598 HEADSET_NREC + "=" + mAudioParams.getOrDefault(HEADSET_NREC, 1599 HEADSET_AUDIO_FEATURE_OFF), 1600 HEADSET_WBS + "=" + mAudioParams.getOrDefault(HEADSET_WBS, 1601 HEADSET_AUDIO_FEATURE_OFF) 1602 }); 1603 Log.i(TAG, "setAudioParameters for " + mDevice + ": " + keyValuePairs); 1604 mSystemInterface.getAudioManager().setParameters(keyValuePairs); 1605 } 1606 1607 private String parseUnknownAt(String atString) { 1608 StringBuilder atCommand = new StringBuilder(atString.length()); 1609 1610 for (int i = 0; i < atString.length(); i++) { 1611 char c = atString.charAt(i); 1612 if (c == '"') { 1613 int j = atString.indexOf('"', i + 1); // search for closing " 1614 if (j == -1) { // unmatched ", insert one. 1615 atCommand.append(atString.substring(i, atString.length())); 1616 atCommand.append('"'); 1617 break; 1618 } 1619 atCommand.append(atString.substring(i, j + 1)); 1620 i = j; 1621 } else if (c != ' ') { 1622 atCommand.append(Character.toUpperCase(c)); 1623 } 1624 } 1625 return atCommand.toString(); 1626 } 1627 1628 private int getAtCommandType(String atCommand) { 1629 int commandType = AtPhonebook.TYPE_UNKNOWN; 1630 String atString = null; 1631 atCommand = atCommand.trim(); 1632 if (atCommand.length() > 5) { 1633 atString = atCommand.substring(5); 1634 if (atString.startsWith("?")) { // Read 1635 commandType = AtPhonebook.TYPE_READ; 1636 } else if (atString.startsWith("=?")) { // Test 1637 commandType = AtPhonebook.TYPE_TEST; 1638 } else if (atString.startsWith("=")) { // Set 1639 commandType = AtPhonebook.TYPE_SET; 1640 } else { 1641 commandType = AtPhonebook.TYPE_UNKNOWN; 1642 } 1643 } 1644 return commandType; 1645 } 1646 1647 /* Method to check if Virtual Call in Progress */ 1648 private boolean isVirtualCallInProgress() { 1649 return mVirtualCallStarted; 1650 } 1651 1652 private void setVirtualCallInProgress(boolean state) { 1653 mVirtualCallStarted = state; 1654 } 1655 1656 // NOTE: Currently the VirtualCall API does not support handling of call transfers. If it is 1657 // initiated from the handsfree device, HeadsetStateMachine will end the virtual call by 1658 // calling terminateScoUsingVirtualVoiceCall() in broadcastAudioState() 1659 private boolean initiateScoUsingVirtualVoiceCall() { 1660 log("initiateScoUsingVirtualVoiceCall: Received"); 1661 // 1. Check if the SCO state is idle 1662 if (mSystemInterface.isInCall() || mVoiceRecognitionStarted) { 1663 Log.e(TAG, "initiateScoUsingVirtualVoiceCall: Call in progress."); 1664 return false; 1665 } 1666 1667 // 2. Send virtual phone state changed to initialize SCO 1668 processCallState(new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_DIALING, "", 0), 1669 true); 1670 processCallState(new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_ALERTING, "", 0), 1671 true); 1672 processCallState(new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0), 1673 true); 1674 setVirtualCallInProgress(true); 1675 // Done 1676 log("initiateScoUsingVirtualVoiceCall: Done"); 1677 return true; 1678 } 1679 1680 private synchronized boolean terminateScoUsingVirtualVoiceCall() { 1681 log("terminateScoUsingVirtualVoiceCall: Received"); 1682 1683 if (!isVirtualCallInProgress()) { 1684 Log.w(TAG, "terminateScoUsingVirtualVoiceCall: No present call to terminate"); 1685 return false; 1686 } 1687 1688 // 2. Send virtual phone state changed to close SCO 1689 processCallState(new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0), 1690 true); 1691 setVirtualCallInProgress(false); 1692 // Done 1693 log("terminateScoUsingVirtualVoiceCall: Done"); 1694 return true; 1695 } 1696 1697 1698 private void processDialCall(String number, BluetoothDevice device) { 1699 String dialNumber; 1700 if (mDialingOut) { 1701 log("processDialCall, already dialling"); 1702 mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1703 return; 1704 } 1705 if ((number == null) || (number.length() == 0)) { 1706 dialNumber = mPhonebook.getLastDialledNumber(); 1707 if (dialNumber == null) { 1708 log("processDialCall, last dial number null"); 1709 mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1710 return; 1711 } 1712 } else if (number.charAt(0) == '>') { 1713 // Yuck - memory dialling requested. 1714 // Just dial last number for now 1715 if (number.startsWith(">9999")) { // for PTS test 1716 mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1717 return; 1718 } 1719 log("processDialCall, memory dial do last dial for now"); 1720 dialNumber = mPhonebook.getLastDialledNumber(); 1721 if (dialNumber == null) { 1722 log("processDialCall, last dial number null"); 1723 mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1724 return; 1725 } 1726 } else { 1727 // Remove trailing ';' 1728 if (number.charAt(number.length() - 1) == ';') { 1729 number = number.substring(0, number.length() - 1); 1730 } 1731 1732 dialNumber = PhoneNumberUtils.convertPreDial(number); 1733 } 1734 // Check for virtual call to terminate before sending Call Intent 1735 terminateScoUsingVirtualVoiceCall(); 1736 mHeadsetService.setActiveDevice(mDevice); 1737 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, 1738 Uri.fromParts(SCHEME_TEL, dialNumber, null)); 1739 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1740 mHeadsetService.startActivity(intent); 1741 // TODO(BT) continue send OK reults code after call starts 1742 // hold wait lock, start a timer, set wait call flag 1743 // Get call started indication from bluetooth phone 1744 mDialingOut = true; 1745 sendMessageDelayed(DIALING_OUT_TIMEOUT, device, DIALING_OUT_TIMEOUT_MS); 1746 } 1747 1748 private void processVolumeEvent(int volumeType, int volume) { 1749 // Only current active device can change SCO volume 1750 if (!mDevice.equals(mHeadsetService.getActiveDevice())) { 1751 Log.w(TAG, "processVolumeEvent, ignored because " + mDevice + " is not active"); 1752 return; 1753 } 1754 if (volumeType == HeadsetHalConstants.VOLUME_TYPE_SPK) { 1755 mSpeakerVolume = volume; 1756 int flag = (getCurrentState() == mAudioOn) ? AudioManager.FLAG_SHOW_UI : 0; 1757 mSystemInterface.getAudioManager() 1758 .setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, volume, flag); 1759 } else if (volumeType == HeadsetHalConstants.VOLUME_TYPE_MIC) { 1760 // Not used currently 1761 mMicVolume = volume; 1762 } else { 1763 Log.e(TAG, "Bad volume type: " + volumeType); 1764 } 1765 } 1766 1767 private void processCallState(HeadsetCallState callState, boolean isVirtualCall) { 1768 mSystemInterface.getHeadsetPhoneState().setNumActiveCall(callState.mNumActive); 1769 mSystemInterface.getHeadsetPhoneState().setNumHeldCall(callState.mNumHeld); 1770 mSystemInterface.getHeadsetPhoneState().setCallState(callState.mCallState); 1771 if (mDialingOut) { 1772 if (callState.mCallState == HeadsetHalConstants.CALL_STATE_DIALING) { 1773 if (!hasMessages(DIALING_OUT_TIMEOUT)) { 1774 return; 1775 } 1776 mHeadsetService.setActiveDevice(mDevice); 1777 mNativeInterface.atResponseCode(mDevice, HeadsetHalConstants.AT_RESPONSE_OK, 0); 1778 removeMessages(DIALING_OUT_TIMEOUT); 1779 } else if (callState.mCallState == HeadsetHalConstants.CALL_STATE_ACTIVE 1780 || callState.mCallState == HeadsetHalConstants.CALL_STATE_IDLE) { 1781 mDialingOut = false; 1782 } 1783 } 1784 1785 log("processCallState: mNumActive: " + callState.mNumActive + " mNumHeld: " 1786 + callState.mNumHeld + " mCallState: " + callState.mCallState); 1787 log("processCallState: mNumber: " + callState.mNumber + " mType: " + callState.mType); 1788 1789 if (isVirtualCall) { 1790 // virtual call state update 1791 if (getCurrentState() != mDisconnected) { 1792 mNativeInterface.phoneStateChange(mDevice, callState); 1793 } 1794 } else { 1795 // circuit-switch voice call update 1796 // stop virtual voice call if there is a CSV call ongoing 1797 if (callState.mNumActive > 0 || callState.mNumHeld > 0 1798 || callState.mCallState != HeadsetHalConstants.CALL_STATE_IDLE) { 1799 terminateScoUsingVirtualVoiceCall(); 1800 } 1801 1802 // Specific handling for case of starting MO/MT call while VOIP 1803 // ongoing, terminateScoUsingVirtualVoiceCall() resets callState 1804 // INCOMING/DIALING to IDLE. Some HS send AT+CIND? to read call 1805 // and get wrong value of callsetup. This case is hit only 1806 // SCO for VOIP call is not terminated via SDK API call. 1807 if (mSystemInterface.getHeadsetPhoneState().getCallState() != callState.mCallState) { 1808 mSystemInterface.getHeadsetPhoneState().setCallState(callState.mCallState); 1809 } 1810 1811 // at this step: if there is virtual call ongoing, it means there is no CSV call 1812 // let virtual call continue and skip phone state update 1813 if (!isVirtualCallInProgress()) { 1814 if (getCurrentState() != mDisconnected) { 1815 mNativeInterface.phoneStateChange(mDevice, callState); 1816 } 1817 } 1818 } 1819 } 1820 1821 private void processNoiseReductionEvent(boolean enable) { 1822 String prevNrec = mAudioParams.getOrDefault(HEADSET_NREC, HEADSET_AUDIO_FEATURE_OFF); 1823 String newNrec = enable ? HEADSET_AUDIO_FEATURE_ON : HEADSET_AUDIO_FEATURE_OFF; 1824 mAudioParams.put(HEADSET_NREC, newNrec); 1825 log("processNoiseReductionEvent: " + HEADSET_NREC + " change " + prevNrec + " -> " 1826 + newNrec); 1827 if (getAudioState() == BluetoothHeadset.STATE_AUDIO_CONNECTED) { 1828 setAudioParameters(); 1829 } 1830 } 1831 1832 private void processWBSEvent(int wbsConfig) { 1833 String prevWbs = mAudioParams.getOrDefault(HEADSET_WBS, HEADSET_AUDIO_FEATURE_OFF); 1834 switch (wbsConfig) { 1835 case HeadsetHalConstants.BTHF_WBS_YES: 1836 mAudioParams.put(HEADSET_WBS, HEADSET_AUDIO_FEATURE_ON); 1837 break; 1838 case HeadsetHalConstants.BTHF_WBS_NO: 1839 case HeadsetHalConstants.BTHF_WBS_NONE: 1840 mAudioParams.put(HEADSET_WBS, HEADSET_AUDIO_FEATURE_OFF); 1841 break; 1842 default: 1843 Log.e(TAG, "processWBSEvent: unknown wbsConfig " + wbsConfig); 1844 return; 1845 } 1846 log("processWBSEvent: " + HEADSET_NREC + " change " + prevWbs + " -> " + mAudioParams.get( 1847 HEADSET_WBS)); 1848 } 1849 1850 private void processAtChld(int chld, BluetoothDevice device) { 1851 if (mSystemInterface.processChld(chld)) { 1852 mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0); 1853 } else { 1854 mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1855 } 1856 } 1857 1858 private void processSubscriberNumberRequest(BluetoothDevice device) { 1859 String number = mSystemInterface.getSubscriberNumber(); 1860 if (number != null) { 1861 mNativeInterface.atResponseString(device, 1862 "+CNUM: ,\"" + number + "\"," + PhoneNumberUtils.toaFromString(number) + ",,4"); 1863 mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0); 1864 } else { 1865 Log.e(TAG, "getSubscriberNumber returns null"); 1866 mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1867 } 1868 } 1869 1870 private void processAtCind(BluetoothDevice device) { 1871 int call, callSetup; 1872 final HeadsetPhoneState phoneState = mSystemInterface.getHeadsetPhoneState(); 1873 1874 /* Handsfree carkits expect that +CIND is properly responded to 1875 Hence we ensure that a proper response is sent 1876 for the virtual call too.*/ 1877 if (isVirtualCallInProgress()) { 1878 call = 1; 1879 callSetup = 0; 1880 } else { 1881 // regular phone call 1882 call = phoneState.getNumActiveCall(); 1883 callSetup = phoneState.getNumHeldCall(); 1884 } 1885 1886 mNativeInterface.cindResponse(device, phoneState.getCindService(), call, callSetup, 1887 phoneState.getCallState(), phoneState.getCindSignal(), phoneState.getCindRoam(), 1888 phoneState.getCindBatteryCharge()); 1889 } 1890 1891 private void processAtCops(BluetoothDevice device) { 1892 String operatorName = mSystemInterface.getNetworkOperator(); 1893 if (operatorName == null) { 1894 operatorName = ""; 1895 } 1896 mNativeInterface.copsResponse(device, operatorName); 1897 } 1898 1899 private void processAtClcc(BluetoothDevice device) { 1900 if (isVirtualCallInProgress()) { 1901 // In virtual call, send our phone number instead of remote phone number 1902 String phoneNumber = mSystemInterface.getSubscriberNumber(); 1903 if (phoneNumber == null) { 1904 phoneNumber = ""; 1905 } 1906 int type = PhoneNumberUtils.toaFromString(phoneNumber); 1907 mNativeInterface.clccResponse(device, 1, 0, 0, 0, false, phoneNumber, type); 1908 mNativeInterface.clccResponse(device, 0, 0, 0, 0, false, "", 0); 1909 } else { 1910 // In Telecom call, ask Telecom to send send remote phone number 1911 if (!mSystemInterface.listCurrentCalls()) { 1912 Log.e(TAG, "processAtClcc: failed to list current calls for " + device); 1913 mNativeInterface.clccResponse(device, 0, 0, 0, 0, false, "", 0); 1914 } else { 1915 sendMessageDelayed(CLCC_RSP_TIMEOUT, device, CLCC_RSP_TIMEOUT_MS); 1916 } 1917 } 1918 } 1919 1920 private void processAtCscs(String atString, int type, BluetoothDevice device) { 1921 log("processAtCscs - atString = " + atString); 1922 if (mPhonebook != null) { 1923 mPhonebook.handleCscsCommand(atString, type, device); 1924 } else { 1925 Log.e(TAG, "Phonebook handle null for At+CSCS"); 1926 mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1927 } 1928 } 1929 1930 private void processAtCpbs(String atString, int type, BluetoothDevice device) { 1931 log("processAtCpbs - atString = " + atString); 1932 if (mPhonebook != null) { 1933 mPhonebook.handleCpbsCommand(atString, type, device); 1934 } else { 1935 Log.e(TAG, "Phonebook handle null for At+CPBS"); 1936 mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1937 } 1938 } 1939 1940 private void processAtCpbr(String atString, int type, BluetoothDevice device) { 1941 log("processAtCpbr - atString = " + atString); 1942 if (mPhonebook != null) { 1943 mPhonebook.handleCpbrCommand(atString, type, device); 1944 } else { 1945 Log.e(TAG, "Phonebook handle null for At+CPBR"); 1946 mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 1947 } 1948 } 1949 1950 /** 1951 * Find a character ch, ignoring quoted sections. 1952 * Return input.length() if not found. 1953 */ 1954 private static int findChar(char ch, String input, int fromIndex) { 1955 for (int i = fromIndex; i < input.length(); i++) { 1956 char c = input.charAt(i); 1957 if (c == '"') { 1958 i = input.indexOf('"', i + 1); 1959 if (i == -1) { 1960 return input.length(); 1961 } 1962 } else if (c == ch) { 1963 return i; 1964 } 1965 } 1966 return input.length(); 1967 } 1968 1969 /** 1970 * Break an argument string into individual arguments (comma delimited). 1971 * Integer arguments are turned into Integer objects. Otherwise a String 1972 * object is used. 1973 */ 1974 private static Object[] generateArgs(String input) { 1975 int i = 0; 1976 int j; 1977 ArrayList<Object> out = new ArrayList<Object>(); 1978 while (i <= input.length()) { 1979 j = findChar(',', input, i); 1980 1981 String arg = input.substring(i, j); 1982 try { 1983 out.add(new Integer(arg)); 1984 } catch (NumberFormatException e) { 1985 out.add(arg); 1986 } 1987 1988 i = j + 1; // move past comma 1989 } 1990 return out.toArray(); 1991 } 1992 1993 /** 1994 * Process vendor specific AT commands 1995 * 1996 * @param atString AT command after the "AT+" prefix 1997 * @param device Remote device that has sent this command 1998 */ 1999 private void processVendorSpecificAt(String atString, BluetoothDevice device) { 2000 log("processVendorSpecificAt - atString = " + atString); 2001 2002 // Currently we accept only SET type commands. 2003 int indexOfEqual = atString.indexOf("="); 2004 if (indexOfEqual == -1) { 2005 Log.e(TAG, "processVendorSpecificAt: command type error in " + atString); 2006 mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 2007 return; 2008 } 2009 2010 String command = atString.substring(0, indexOfEqual); 2011 Integer companyId = VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.get(command); 2012 if (companyId == null) { 2013 Log.e(TAG, "processVendorSpecificAt: unsupported command: " + atString); 2014 mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 2015 return; 2016 } 2017 2018 String arg = atString.substring(indexOfEqual + 1); 2019 if (arg.startsWith("?")) { 2020 Log.e(TAG, "processVendorSpecificAt: command type error in " + atString); 2021 mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0); 2022 return; 2023 } 2024 2025 Object[] args = generateArgs(arg); 2026 if (command.equals(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XAPL)) { 2027 processAtXapl(args, device); 2028 } 2029 broadcastVendorSpecificEventIntent(command, companyId, BluetoothHeadset.AT_CMD_TYPE_SET, 2030 args, device); 2031 mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0); 2032 } 2033 2034 /** 2035 * Process AT+XAPL AT command 2036 * 2037 * @param args command arguments after the equal sign 2038 * @param device Remote device that has sent this command 2039 */ 2040 private void processAtXapl(Object[] args, BluetoothDevice device) { 2041 if (args.length != 2) { 2042 Log.w(TAG, "processAtXapl() args length must be 2: " + String.valueOf(args.length)); 2043 return; 2044 } 2045 if (!(args[0] instanceof String) || !(args[1] instanceof Integer)) { 2046 Log.w(TAG, "processAtXapl() argument types not match"); 2047 return; 2048 } 2049 // feature = 2 indicates that we support battery level reporting only 2050 mNativeInterface.atResponseString(device, "+XAPL=iPhone," + String.valueOf(2)); 2051 } 2052 2053 private void processUnknownAt(String atString, BluetoothDevice device) { 2054 if (device == null) { 2055 Log.w(TAG, "processUnknownAt device is null"); 2056 return; 2057 } 2058 log("processUnknownAt - atString = " + atString); 2059 String atCommand = parseUnknownAt(atString); 2060 int commandType = getAtCommandType(atCommand); 2061 if (atCommand.startsWith("+CSCS")) { 2062 processAtCscs(atCommand.substring(5), commandType, device); 2063 } else if (atCommand.startsWith("+CPBS")) { 2064 processAtCpbs(atCommand.substring(5), commandType, device); 2065 } else if (atCommand.startsWith("+CPBR")) { 2066 processAtCpbr(atCommand.substring(5), commandType, device); 2067 } else { 2068 processVendorSpecificAt(atCommand, device); 2069 } 2070 } 2071 2072 private void processKeyPressed(BluetoothDevice device) { 2073 final HeadsetPhoneState phoneState = mSystemInterface.getHeadsetPhoneState(); 2074 if (phoneState.getCallState() == HeadsetHalConstants.CALL_STATE_INCOMING) { 2075 mSystemInterface.answerCall(device); 2076 } else if (phoneState.getNumActiveCall() > 0) { 2077 if (getAudioState() != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { 2078 mHeadsetService.setActiveDevice(mDevice); 2079 mNativeInterface.connectAudio(mDevice); 2080 } else { 2081 mSystemInterface.hangupCall(device, false); 2082 } 2083 } else { 2084 String dialNumber = mPhonebook.getLastDialledNumber(); 2085 if (dialNumber == null) { 2086 log("processKeyPressed, last dial number null"); 2087 return; 2088 } 2089 mHeadsetService.setActiveDevice(mDevice); 2090 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, 2091 Uri.fromParts(SCHEME_TEL, dialNumber, null)); 2092 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 2093 mHeadsetService.startActivity(intent); 2094 } 2095 } 2096 2097 /** 2098 * Send HF indicator value changed intent 2099 * 2100 * @param device Device whose HF indicator value has changed 2101 * @param indId Indicator ID [0-65535] 2102 * @param indValue Indicator Value [0-65535], -1 means invalid but indId is supported 2103 */ 2104 private void sendIndicatorIntent(BluetoothDevice device, int indId, int indValue) { 2105 Intent intent = new Intent(BluetoothHeadset.ACTION_HF_INDICATORS_VALUE_CHANGED); 2106 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 2107 intent.putExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, indId); 2108 intent.putExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, indValue); 2109 2110 mHeadsetService.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM); 2111 } 2112 2113 private void processAtBind(String atString, BluetoothDevice device) { 2114 log("processAtBind: " + atString); 2115 2116 // Parse the AT String to find the Indicator Ids that are supported 2117 int indId = 0; 2118 int iter = 0; 2119 int iter1 = 0; 2120 2121 while (iter < atString.length()) { 2122 iter1 = findChar(',', atString, iter); 2123 String id = atString.substring(iter, iter1); 2124 2125 try { 2126 indId = Integer.valueOf(id); 2127 } catch (NumberFormatException e) { 2128 Log.e(TAG, Log.getStackTraceString(new Throwable())); 2129 } 2130 2131 switch (indId) { 2132 case HeadsetHalConstants.HF_INDICATOR_ENHANCED_DRIVER_SAFETY: 2133 log("Send Broadcast intent for the Enhanced Driver Safety indicator."); 2134 sendIndicatorIntent(device, indId, -1); 2135 break; 2136 case HeadsetHalConstants.HF_INDICATOR_BATTERY_LEVEL_STATUS: 2137 log("Send Broadcast intent for the Battery Level indicator."); 2138 sendIndicatorIntent(device, indId, -1); 2139 break; 2140 default: 2141 log("Invalid HF Indicator Received"); 2142 break; 2143 } 2144 2145 iter = iter1 + 1; // move past comma 2146 } 2147 } 2148 2149 private void processAtBiev(int indId, int indValue, BluetoothDevice device) { 2150 log("processAtBiev: ind_id=" + indId + ", ind_value=" + indValue); 2151 sendIndicatorIntent(device, indId, indValue); 2152 } 2153 2154 private void processSendClccResponse(HeadsetClccResponse clcc) { 2155 if (!hasMessages(CLCC_RSP_TIMEOUT)) { 2156 return; 2157 } 2158 if (clcc.mIndex == 0) { 2159 removeMessages(CLCC_RSP_TIMEOUT); 2160 } 2161 mNativeInterface.clccResponse(mDevice, clcc.mIndex, clcc.mDirection, clcc.mStatus, 2162 clcc.mMode, clcc.mMpty, clcc.mNumber, clcc.mType); 2163 } 2164 2165 private void processSendVendorSpecificResultCode(HeadsetVendorSpecificResultCode resultCode) { 2166 String stringToSend = resultCode.mCommand + ": "; 2167 if (resultCode.mArg != null) { 2168 stringToSend += resultCode.mArg; 2169 } 2170 mNativeInterface.atResponseString(resultCode.mDevice, stringToSend); 2171 } 2172 2173 private String getCurrentDeviceName() { 2174 String deviceName = mAdapterService.getRemoteName(mDevice); 2175 if (deviceName == null) { 2176 return "<unknown>"; 2177 } 2178 return deviceName; 2179 } 2180 2181 // Accept incoming SCO only when there is in-band ringing, incoming call, 2182 // active call, VR activated, active VOIP call 2183 private boolean isScoAcceptable() { 2184 if (mHeadsetService.getForceScoAudio()) { 2185 return true; 2186 } 2187 BluetoothDevice activeDevice = mHeadsetService.getActiveDevice(); 2188 if (!mDevice.equals(activeDevice)) { 2189 Log.w(TAG, "isScoAcceptable: rejected SCO since " + mDevice 2190 + " is not the current active device " + activeDevice); 2191 return false; 2192 } 2193 if (!mHeadsetService.getAudioRouteAllowed()) { 2194 Log.w(TAG, "isScoAcceptabl: rejected SCO since audio route is not allowed"); 2195 return false; 2196 } 2197 if (mSystemInterface.isInCall() || mVoiceRecognitionStarted) { 2198 return true; 2199 } 2200 if (mSystemInterface.isRinging() && mHeadsetService.isInbandRingingEnabled()) { 2201 return true; 2202 } 2203 Log.w(TAG, "isScoAcceptable: rejected SCO, inCall=" + mSystemInterface.isInCall() 2204 + ", voiceRecognition=" + mVoiceRecognitionStarted + ", ringing=" + mSystemInterface 2205 .isRinging() + ", inbandRinging=" + mHeadsetService.isInbandRingingEnabled()); 2206 return false; 2207 } 2208 2209 @Override 2210 protected void log(String msg) { 2211 if (DBG) { 2212 super.log(msg); 2213 } 2214 } 2215 2216 @Override 2217 protected String getLogRecString(Message msg) { 2218 StringBuilder builder = new StringBuilder(); 2219 builder.append(getMessageName(msg.what)); 2220 builder.append(": "); 2221 builder.append("arg1=") 2222 .append(msg.arg1) 2223 .append(", arg2=") 2224 .append(msg.arg2) 2225 .append(", obj="); 2226 if (msg.obj instanceof HeadsetMessageObject) { 2227 HeadsetMessageObject object = (HeadsetMessageObject) msg.obj; 2228 object.buildString(builder); 2229 } else { 2230 builder.append(msg.obj); 2231 } 2232 return builder.toString(); 2233 } 2234 2235 private void handleAccessPermissionResult(Intent intent) { 2236 log("handleAccessPermissionResult"); 2237 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 2238 if (!mPhonebook.getCheckingAccessPermission()) { 2239 return; 2240 } 2241 int atCommandResult = 0; 2242 int atCommandErrorCode = 0; 2243 // HeadsetBase headset = mHandsfree.getHeadset(); 2244 // ASSERT: (headset != null) && headSet.isConnected() 2245 // REASON: mCheckingAccessPermission is true, otherwise resetAtState 2246 // has set mCheckingAccessPermission to false 2247 if (intent.getAction().equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) { 2248 if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT, 2249 BluetoothDevice.CONNECTION_ACCESS_NO) 2250 == BluetoothDevice.CONNECTION_ACCESS_YES) { 2251 if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) { 2252 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED); 2253 } 2254 atCommandResult = mPhonebook.processCpbrCommand(device); 2255 } else { 2256 if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) { 2257 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED); 2258 } 2259 } 2260 } 2261 mPhonebook.setCpbrIndex(-1); 2262 mPhonebook.setCheckingAccessPermission(false); 2263 if (atCommandResult >= 0) { 2264 mNativeInterface.atResponseCode(device, atCommandResult, atCommandErrorCode); 2265 } else { 2266 log("handleAccessPermissionResult - RESULT_NONE"); 2267 } 2268 } 2269 2270 private static String getMessageName(int what) { 2271 switch (what) { 2272 case CONNECT: 2273 return "CONNECT"; 2274 case DISCONNECT: 2275 return "DISCONNECT"; 2276 case CONNECT_AUDIO: 2277 return "CONNECT_AUDIO"; 2278 case DISCONNECT_AUDIO: 2279 return "DISCONNECT_AUDIO"; 2280 case VOICE_RECOGNITION_START: 2281 return "VOICE_RECOGNITION_START"; 2282 case VOICE_RECOGNITION_STOP: 2283 return "VOICE_RECOGNITION_STOP"; 2284 case INTENT_SCO_VOLUME_CHANGED: 2285 return "INTENT_SCO_VOLUME_CHANGED"; 2286 case INTENT_CONNECTION_ACCESS_REPLY: 2287 return "INTENT_CONNECTION_ACCESS_REPLY"; 2288 case CALL_STATE_CHANGED: 2289 return "CALL_STATE_CHANGED"; 2290 case DEVICE_STATE_CHANGED: 2291 return "DEVICE_STATE_CHANGED"; 2292 case SEND_CCLC_RESPONSE: 2293 return "SEND_CCLC_RESPONSE"; 2294 case SEND_VENDOR_SPECIFIC_RESULT_CODE: 2295 return "SEND_VENDOR_SPECIFIC_RESULT_CODE"; 2296 case VIRTUAL_CALL_START: 2297 return "VIRTUAL_CALL_START"; 2298 case VIRTUAL_CALL_STOP: 2299 return "VIRTUAL_CALL_STOP"; 2300 case STACK_EVENT: 2301 return "STACK_EVENT"; 2302 case DIALING_OUT_TIMEOUT: 2303 return "DIALING_OUT_TIMEOUT"; 2304 case START_VR_TIMEOUT: 2305 return "START_VR_TIMEOUT"; 2306 case CLCC_RSP_TIMEOUT: 2307 return "CLCC_RSP_TIMEOUT"; 2308 case CONNECT_TIMEOUT: 2309 return "CONNECT_TIMEOUT"; 2310 default: 2311 return "UNKNOWN"; 2312 } 2313 } 2314} 2315