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