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