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