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