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