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