1 /* 2 * Copyright (C) 2015 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.server.telecom; 18 19 20 import android.app.ActivityManager; 21 import android.content.Context; 22 import android.content.pm.UserInfo; 23 import android.media.AudioManager; 24 import android.media.IAudioService; 25 import android.os.Binder; 26 import android.os.Message; 27 import android.os.RemoteException; 28 import android.os.SystemProperties; 29 import android.os.UserHandle; 30 import android.telecom.CallAudioState; 31 import android.telecom.Log; 32 import android.telecom.Logging.Session; 33 import android.util.SparseArray; 34 35 import com.android.internal.annotations.VisibleForTesting; 36 import com.android.internal.util.IState; 37 import com.android.internal.util.IndentingPrintWriter; 38 import com.android.internal.util.State; 39 import com.android.internal.util.StateMachine; 40 import com.android.server.telecom.bluetooth.BluetoothRouteManager; 41 42 import java.util.HashMap; 43 44 /** 45 * This class describes the available routes of a call as a state machine. 46 * Transitions are caused solely by the commands sent as messages. Possible values for msg.what 47 * are defined as event constants in this file. 48 * 49 * The eight states are all instances of the abstract base class, {@link AudioState}. Each state 50 * is a combination of one of the four audio routes (earpiece, wired headset, bluetooth, and 51 * speakerphone) and audio focus status (active or quiescent). 52 * 53 * Messages are processed first by the processMessage method in the base class, AudioState. 54 * Any messages not completely handled by AudioState are further processed by the same method in 55 * the route-specific abstract classes: {@link EarpieceRoute}, {@link HeadsetRoute}, 56 * {@link BluetoothRoute}, and {@link SpeakerRoute}. Finally, messages that are not handled at 57 * this level are then processed by the classes corresponding to the state instances themselves. 58 * 59 * There are several variables carrying additional state. These include: 60 * mAvailableRoutes: A bitmask describing which audio routes are available 61 * mWasOnSpeaker: A boolean indicating whether we should switch to speakerphone after disconnecting 62 * from a wired headset 63 * mIsMuted: a boolean indicating whether the audio is muted 64 */ 65 public class CallAudioRouteStateMachine extends StateMachine { 66 private static final String TELECOM_PACKAGE = 67 CallAudioRouteStateMachine.class.getPackage().getName(); 68 69 /** Direct the audio stream through the device's earpiece. */ 70 public static final int ROUTE_EARPIECE = CallAudioState.ROUTE_EARPIECE; 71 72 /** Direct the audio stream through Bluetooth. */ 73 public static final int ROUTE_BLUETOOTH = CallAudioState.ROUTE_BLUETOOTH; 74 75 /** Direct the audio stream through a wired headset. */ 76 public static final int ROUTE_WIRED_HEADSET = CallAudioState.ROUTE_WIRED_HEADSET; 77 78 /** Direct the audio stream through the device's speakerphone. */ 79 public static final int ROUTE_SPEAKER = CallAudioState.ROUTE_SPEAKER; 80 81 /** Valid values for msg.what */ 82 public static final int CONNECT_WIRED_HEADSET = 1; 83 public static final int DISCONNECT_WIRED_HEADSET = 2; 84 public static final int CONNECT_BLUETOOTH = 3; 85 public static final int DISCONNECT_BLUETOOTH = 4; 86 public static final int CONNECT_DOCK = 5; 87 public static final int DISCONNECT_DOCK = 6; 88 89 public static final int SWITCH_EARPIECE = 1001; 90 public static final int SWITCH_BLUETOOTH = 1002; 91 public static final int SWITCH_HEADSET = 1003; 92 public static final int SWITCH_SPEAKER = 1004; 93 // Wired headset, earpiece, or speakerphone, in that order of precedence. 94 public static final int SWITCH_BASELINE_ROUTE = 1005; 95 public static final int BT_AUDIO_DISCONNECT = 1006; 96 97 public static final int USER_SWITCH_EARPIECE = 1101; 98 public static final int USER_SWITCH_BLUETOOTH = 1102; 99 public static final int USER_SWITCH_HEADSET = 1103; 100 public static final int USER_SWITCH_SPEAKER = 1104; 101 public static final int USER_SWITCH_BASELINE_ROUTE = 1105; 102 103 public static final int UPDATE_SYSTEM_AUDIO_ROUTE = 1201; 104 105 public static final int MUTE_ON = 3001; 106 public static final int MUTE_OFF = 3002; 107 public static final int TOGGLE_MUTE = 3003; 108 109 public static final int SWITCH_FOCUS = 4001; 110 111 // Used in testing to execute verifications. Not compatible with subsessions. 112 public static final int RUN_RUNNABLE = 9001; 113 114 /** Valid values for mAudioFocusType */ 115 public static final int NO_FOCUS = 1; 116 public static final int ACTIVE_FOCUS = 2; 117 public static final int RINGING_FOCUS = 3; 118 119 /** Valid values for the argument for SWITCH_BASELINE_ROUTE */ 120 public static final int NO_INCLUDE_BLUETOOTH_IN_BASELINE = 0; 121 public static final int INCLUDE_BLUETOOTH_IN_BASELINE = 1; 122 123 @VisibleForTesting 124 public static final SparseArray<String> AUDIO_ROUTE_TO_LOG_EVENT = new SparseArray<String>() {{ 125 put(CallAudioState.ROUTE_BLUETOOTH, LogUtils.Events.AUDIO_ROUTE_BT); 126 put(CallAudioState.ROUTE_EARPIECE, LogUtils.Events.AUDIO_ROUTE_EARPIECE); 127 put(CallAudioState.ROUTE_SPEAKER, LogUtils.Events.AUDIO_ROUTE_SPEAKER); 128 put(CallAudioState.ROUTE_WIRED_HEADSET, LogUtils.Events.AUDIO_ROUTE_HEADSET); 129 }}; 130 131 private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{ 132 put(CONNECT_WIRED_HEADSET, "CONNECT_WIRED_HEADSET"); 133 put(DISCONNECT_WIRED_HEADSET, "DISCONNECT_WIRED_HEADSET"); 134 put(CONNECT_BLUETOOTH, "CONNECT_BLUETOOTH"); 135 put(DISCONNECT_BLUETOOTH, "DISCONNECT_BLUETOOTH"); 136 put(CONNECT_DOCK, "CONNECT_DOCK"); 137 put(DISCONNECT_DOCK, "DISCONNECT_DOCK"); 138 139 put(SWITCH_EARPIECE, "SWITCH_EARPIECE"); 140 put(SWITCH_BLUETOOTH, "SWITCH_BLUETOOTH"); 141 put(SWITCH_HEADSET, "SWITCH_HEADSET"); 142 put(SWITCH_SPEAKER, "SWITCH_SPEAKER"); 143 put(SWITCH_BASELINE_ROUTE, "SWITCH_BASELINE_ROUTE"); 144 put(BT_AUDIO_DISCONNECT, "BT_AUDIO_DISCONNECT"); 145 146 put(USER_SWITCH_EARPIECE, "USER_SWITCH_EARPIECE"); 147 put(USER_SWITCH_BLUETOOTH, "USER_SWITCH_BLUETOOTH"); 148 put(USER_SWITCH_HEADSET, "USER_SWITCH_HEADSET"); 149 put(USER_SWITCH_SPEAKER, "USER_SWITCH_SPEAKER"); 150 put(USER_SWITCH_BASELINE_ROUTE, "USER_SWITCH_BASELINE_ROUTE"); 151 152 put(UPDATE_SYSTEM_AUDIO_ROUTE, "UPDATE_SYSTEM_AUDIO_ROUTE"); 153 154 put(MUTE_ON, "MUTE_ON"); 155 put(MUTE_OFF, "MUTE_OFF"); 156 put(TOGGLE_MUTE, "TOGGLE_MUTE"); 157 158 put(SWITCH_FOCUS, "SWITCH_FOCUS"); 159 160 put(RUN_RUNNABLE, "RUN_RUNNABLE"); 161 }}; 162 163 private static final String ACTIVE_EARPIECE_ROUTE_NAME = "ActiveEarpieceRoute"; 164 private static final String ACTIVE_BLUETOOTH_ROUTE_NAME = "ActiveBluetoothRoute"; 165 private static final String ACTIVE_SPEAKER_ROUTE_NAME = "ActiveSpeakerRoute"; 166 private static final String ACTIVE_HEADSET_ROUTE_NAME = "ActiveHeadsetRoute"; 167 private static final String RINGING_BLUETOOTH_ROUTE_NAME = "RingingBluetoothRoute"; 168 private static final String QUIESCENT_EARPIECE_ROUTE_NAME = "QuiescentEarpieceRoute"; 169 private static final String QUIESCENT_BLUETOOTH_ROUTE_NAME = "QuiescentBluetoothRoute"; 170 private static final String QUIESCENT_SPEAKER_ROUTE_NAME = "QuiescentSpeakerRoute"; 171 private static final String QUIESCENT_HEADSET_ROUTE_NAME = "QuiescentHeadsetRoute"; 172 173 public static final String NAME = CallAudioRouteStateMachine.class.getName(); 174 175 @Override onPreHandleMessage(Message msg)176 protected void onPreHandleMessage(Message msg) { 177 if (msg.obj != null && msg.obj instanceof Session) { 178 String messageCodeName = MESSAGE_CODE_TO_NAME.get(msg.what, "unknown"); 179 Log.continueSession((Session) msg.obj, "CARSM.pM_" + messageCodeName); 180 Log.i(this, "Message received: %s=%d, arg1=%d", messageCodeName, msg.what, msg.arg1); 181 } 182 } 183 184 @Override onPostHandleMessage(Message msg)185 protected void onPostHandleMessage(Message msg) { 186 Log.endSession(); 187 } 188 189 abstract class AudioState extends State { 190 @Override enter()191 public void enter() { 192 super.enter(); 193 Log.addEvent(mCallsManager.getForegroundCall(), LogUtils.Events.AUDIO_ROUTE, 194 "Entering state " + getName()); 195 if (isActive()) { 196 Log.addEvent(mCallsManager.getForegroundCall(), 197 AUDIO_ROUTE_TO_LOG_EVENT.get(getRouteCode(), LogUtils.Events.AUDIO_ROUTE)); 198 } 199 } 200 201 @Override exit()202 public void exit() { 203 Log.addEvent(mCallsManager.getForegroundCall(), LogUtils.Events.AUDIO_ROUTE, 204 "Leaving state " + getName()); 205 super.exit(); 206 } 207 208 @Override processMessage(Message msg)209 public boolean processMessage(Message msg) { 210 int addedRoutes = 0; 211 int removedRoutes = 0; 212 213 switch (msg.what) { 214 case CONNECT_WIRED_HEADSET: 215 Log.addEvent(mCallsManager.getForegroundCall(), LogUtils.Events.AUDIO_ROUTE, 216 "Wired headset connected"); 217 removedRoutes |= ROUTE_EARPIECE; 218 addedRoutes |= ROUTE_WIRED_HEADSET; 219 break; 220 case CONNECT_BLUETOOTH: 221 Log.addEvent(mCallsManager.getForegroundCall(), LogUtils.Events.AUDIO_ROUTE, 222 "Bluetooth connected"); 223 addedRoutes |= ROUTE_BLUETOOTH; 224 break; 225 case DISCONNECT_WIRED_HEADSET: 226 Log.addEvent(mCallsManager.getForegroundCall(), LogUtils.Events.AUDIO_ROUTE, 227 "Wired headset disconnected"); 228 removedRoutes |= ROUTE_WIRED_HEADSET; 229 if (mDoesDeviceSupportEarpieceRoute) { 230 addedRoutes |= ROUTE_EARPIECE; 231 } 232 break; 233 case DISCONNECT_BLUETOOTH: 234 Log.addEvent(mCallsManager.getForegroundCall(), LogUtils.Events.AUDIO_ROUTE, 235 "Bluetooth disconnected"); 236 removedRoutes |= ROUTE_BLUETOOTH; 237 break; 238 case SWITCH_BASELINE_ROUTE: 239 sendInternalMessage(calculateBaselineRouteMessage(false, 240 msg.arg1 == INCLUDE_BLUETOOTH_IN_BASELINE)); 241 return HANDLED; 242 case USER_SWITCH_BASELINE_ROUTE: 243 sendInternalMessage(calculateBaselineRouteMessage(true, 244 msg.arg1 == INCLUDE_BLUETOOTH_IN_BASELINE)); 245 return HANDLED; 246 case SWITCH_FOCUS: 247 mAudioFocusType = msg.arg1; 248 return NOT_HANDLED; 249 default: 250 return NOT_HANDLED; 251 } 252 253 if (addedRoutes != 0 || removedRoutes != 0) { 254 mAvailableRoutes = modifyRoutes(mAvailableRoutes, removedRoutes, addedRoutes, true); 255 mDeviceSupportedRoutes = modifyRoutes(mDeviceSupportedRoutes, removedRoutes, 256 addedRoutes, false); 257 } 258 259 return NOT_HANDLED; 260 } 261 262 // Behavior will depend on whether the state is an active one or a quiescent one. updateSystemAudioState()263 abstract public void updateSystemAudioState(); isActive()264 abstract public boolean isActive(); getRouteCode()265 abstract public int getRouteCode(); 266 } 267 268 class ActiveEarpieceRoute extends EarpieceRoute { 269 @Override getName()270 public String getName() { 271 return ACTIVE_EARPIECE_ROUTE_NAME; 272 } 273 274 @Override isActive()275 public boolean isActive() { 276 return true; 277 } 278 279 @Override enter()280 public void enter() { 281 super.enter(); 282 setSpeakerphoneOn(false); 283 setBluetoothOn(false); 284 CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_EARPIECE, 285 mAvailableRoutes); 286 setSystemAudioState(newState, true); 287 updateInternalCallAudioState(); 288 } 289 290 @Override updateSystemAudioState()291 public void updateSystemAudioState() { 292 updateInternalCallAudioState(); 293 setSystemAudioState(mCurrentCallAudioState); 294 } 295 296 @Override processMessage(Message msg)297 public boolean processMessage(Message msg) { 298 if (super.processMessage(msg) == HANDLED) { 299 return HANDLED; 300 } 301 switch (msg.what) { 302 case SWITCH_EARPIECE: 303 case USER_SWITCH_EARPIECE: 304 // Nothing to do here 305 return HANDLED; 306 case SWITCH_BLUETOOTH: 307 case USER_SWITCH_BLUETOOTH: 308 if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) { 309 transitionTo(mAudioFocusType == ACTIVE_FOCUS ? 310 mActiveBluetoothRoute : mRingingBluetoothRoute); 311 } else { 312 Log.w(this, "Ignoring switch to bluetooth command. Not available."); 313 } 314 return HANDLED; 315 case SWITCH_HEADSET: 316 case USER_SWITCH_HEADSET: 317 if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) { 318 transitionTo(mActiveHeadsetRoute); 319 } else { 320 Log.w(this, "Ignoring switch to headset command. Not available."); 321 } 322 return HANDLED; 323 case SWITCH_SPEAKER: 324 case USER_SWITCH_SPEAKER: 325 transitionTo(mActiveSpeakerRoute); 326 return HANDLED; 327 case SWITCH_FOCUS: 328 if (msg.arg1 == NO_FOCUS) { 329 reinitialize(); 330 } 331 return HANDLED; 332 default: 333 return NOT_HANDLED; 334 } 335 } 336 } 337 338 class QuiescentEarpieceRoute extends EarpieceRoute { 339 @Override getName()340 public String getName() { 341 return QUIESCENT_EARPIECE_ROUTE_NAME; 342 } 343 344 @Override isActive()345 public boolean isActive() { 346 return false; 347 } 348 349 @Override enter()350 public void enter() { 351 super.enter(); 352 mHasUserExplicitlyLeftBluetooth = false; 353 updateInternalCallAudioState(); 354 } 355 356 @Override updateSystemAudioState()357 public void updateSystemAudioState() { 358 updateInternalCallAudioState(); 359 } 360 361 @Override processMessage(Message msg)362 public boolean processMessage(Message msg) { 363 if (super.processMessage(msg) == HANDLED) { 364 return HANDLED; 365 } 366 switch (msg.what) { 367 case SWITCH_EARPIECE: 368 case USER_SWITCH_EARPIECE: 369 // Nothing to do here 370 return HANDLED; 371 case SWITCH_BLUETOOTH: 372 case USER_SWITCH_BLUETOOTH: 373 if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) { 374 transitionTo(mQuiescentBluetoothRoute); 375 } else { 376 Log.w(this, "Ignoring switch to bluetooth command. Not available."); 377 } 378 return HANDLED; 379 case SWITCH_HEADSET: 380 case USER_SWITCH_HEADSET: 381 if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) { 382 transitionTo(mQuiescentHeadsetRoute); 383 } else { 384 Log.w(this, "Ignoring switch to headset command. Not available."); 385 } 386 return HANDLED; 387 case SWITCH_SPEAKER: 388 case USER_SWITCH_SPEAKER: 389 transitionTo(mQuiescentSpeakerRoute); 390 return HANDLED; 391 case SWITCH_FOCUS: 392 if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) { 393 transitionTo(mActiveEarpieceRoute); 394 } 395 return HANDLED; 396 default: 397 return NOT_HANDLED; 398 } 399 } 400 } 401 402 abstract class EarpieceRoute extends AudioState { 403 @Override getRouteCode()404 public int getRouteCode() { 405 return CallAudioState.ROUTE_EARPIECE; 406 } 407 408 @Override processMessage(Message msg)409 public boolean processMessage(Message msg) { 410 if (super.processMessage(msg) == HANDLED) { 411 return HANDLED; 412 } 413 switch (msg.what) { 414 case CONNECT_WIRED_HEADSET: 415 sendInternalMessage(SWITCH_HEADSET); 416 return HANDLED; 417 case CONNECT_BLUETOOTH: 418 if (!mHasUserExplicitlyLeftBluetooth) { 419 sendInternalMessage(SWITCH_BLUETOOTH); 420 } else { 421 Log.i(this, "Not switching to BT route from earpiece because user has " + 422 "explicitly disconnected."); 423 updateSystemAudioState(); 424 } 425 return HANDLED; 426 case DISCONNECT_BLUETOOTH: 427 updateSystemAudioState(); 428 // No change in audio route required 429 return HANDLED; 430 case DISCONNECT_WIRED_HEADSET: 431 Log.e(this, new IllegalStateException(), 432 "Wired headset should not go from connected to not when on " + 433 "earpiece"); 434 updateSystemAudioState(); 435 return HANDLED; 436 case BT_AUDIO_DISCONNECT: 437 // This may be sent as a confirmation by the BT stack after switch off BT. 438 return HANDLED; 439 case CONNECT_DOCK: 440 sendInternalMessage(SWITCH_SPEAKER); 441 return HANDLED; 442 case DISCONNECT_DOCK: 443 // Nothing to do here 444 return HANDLED; 445 default: 446 return NOT_HANDLED; 447 } 448 } 449 } 450 451 class ActiveHeadsetRoute extends HeadsetRoute { 452 @Override getName()453 public String getName() { 454 return ACTIVE_HEADSET_ROUTE_NAME; 455 } 456 457 @Override isActive()458 public boolean isActive() { 459 return true; 460 } 461 462 @Override enter()463 public void enter() { 464 super.enter(); 465 setSpeakerphoneOn(false); 466 setBluetoothOn(false); 467 CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_WIRED_HEADSET, 468 mAvailableRoutes); 469 setSystemAudioState(newState, true); 470 updateInternalCallAudioState(); 471 } 472 473 @Override updateSystemAudioState()474 public void updateSystemAudioState() { 475 updateInternalCallAudioState(); 476 setSystemAudioState(mCurrentCallAudioState); 477 } 478 479 @Override processMessage(Message msg)480 public boolean processMessage(Message msg) { 481 if (super.processMessage(msg) == HANDLED) { 482 return HANDLED; 483 } 484 switch (msg.what) { 485 case SWITCH_EARPIECE: 486 case USER_SWITCH_EARPIECE: 487 if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) { 488 transitionTo(mActiveEarpieceRoute); 489 } else { 490 Log.w(this, "Ignoring switch to earpiece command. Not available."); 491 } 492 return HANDLED; 493 case SWITCH_BLUETOOTH: 494 case USER_SWITCH_BLUETOOTH: 495 if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) { 496 transitionTo(mAudioFocusType == ACTIVE_FOCUS ? 497 mActiveBluetoothRoute : mRingingBluetoothRoute); 498 } else { 499 Log.w(this, "Ignoring switch to bluetooth command. Not available."); 500 } 501 return HANDLED; 502 case SWITCH_HEADSET: 503 case USER_SWITCH_HEADSET: 504 // Nothing to do 505 return HANDLED; 506 case SWITCH_SPEAKER: 507 case USER_SWITCH_SPEAKER: 508 transitionTo(mActiveSpeakerRoute); 509 return HANDLED; 510 case SWITCH_FOCUS: 511 if (msg.arg1 == NO_FOCUS) { 512 reinitialize(); 513 } 514 return HANDLED; 515 default: 516 return NOT_HANDLED; 517 } 518 } 519 } 520 521 class QuiescentHeadsetRoute extends HeadsetRoute { 522 @Override getName()523 public String getName() { 524 return QUIESCENT_HEADSET_ROUTE_NAME; 525 } 526 527 @Override isActive()528 public boolean isActive() { 529 return false; 530 } 531 532 @Override enter()533 public void enter() { 534 super.enter(); 535 mHasUserExplicitlyLeftBluetooth = false; 536 updateInternalCallAudioState(); 537 } 538 539 @Override updateSystemAudioState()540 public void updateSystemAudioState() { 541 updateInternalCallAudioState(); 542 } 543 544 @Override processMessage(Message msg)545 public boolean processMessage(Message msg) { 546 if (super.processMessage(msg) == HANDLED) { 547 return HANDLED; 548 } 549 switch (msg.what) { 550 case SWITCH_EARPIECE: 551 case USER_SWITCH_EARPIECE: 552 if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) { 553 transitionTo(mQuiescentEarpieceRoute); 554 } else { 555 Log.w(this, "Ignoring switch to earpiece command. Not available."); 556 } 557 return HANDLED; 558 case SWITCH_BLUETOOTH: 559 case USER_SWITCH_BLUETOOTH: 560 if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) { 561 transitionTo(mQuiescentBluetoothRoute); 562 } else { 563 Log.w(this, "Ignoring switch to bluetooth command. Not available."); 564 } 565 return HANDLED; 566 case SWITCH_HEADSET: 567 case USER_SWITCH_HEADSET: 568 // Nothing to do 569 return HANDLED; 570 case SWITCH_SPEAKER: 571 case USER_SWITCH_SPEAKER: 572 transitionTo(mQuiescentSpeakerRoute); 573 return HANDLED; 574 case SWITCH_FOCUS: 575 if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) { 576 transitionTo(mActiveHeadsetRoute); 577 } 578 return HANDLED; 579 default: 580 return NOT_HANDLED; 581 } 582 } 583 } 584 585 abstract class HeadsetRoute extends AudioState { 586 @Override getRouteCode()587 public int getRouteCode() { 588 return CallAudioState.ROUTE_WIRED_HEADSET; 589 } 590 591 @Override processMessage(Message msg)592 public boolean processMessage(Message msg) { 593 if (super.processMessage(msg) == HANDLED) { 594 return HANDLED; 595 } 596 switch (msg.what) { 597 case CONNECT_WIRED_HEADSET: 598 Log.e(this, new IllegalStateException(), 599 "Wired headset should already be connected."); 600 mAvailableRoutes |= ROUTE_WIRED_HEADSET; 601 updateSystemAudioState(); 602 return HANDLED; 603 case CONNECT_BLUETOOTH: 604 if (!mHasUserExplicitlyLeftBluetooth) { 605 sendInternalMessage(SWITCH_BLUETOOTH); 606 } else { 607 Log.i(this, "Not switching to BT route from headset because user has " + 608 "explicitly disconnected."); 609 updateSystemAudioState(); 610 } 611 return HANDLED; 612 case DISCONNECT_BLUETOOTH: 613 updateSystemAudioState(); 614 // No change in audio route required 615 return HANDLED; 616 case DISCONNECT_WIRED_HEADSET: 617 if (mWasOnSpeaker) { 618 sendInternalMessage(SWITCH_SPEAKER); 619 } else { 620 sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE); 621 } 622 return HANDLED; 623 case BT_AUDIO_DISCONNECT: 624 // This may be sent as a confirmation by the BT stack after switch off BT. 625 return HANDLED; 626 case CONNECT_DOCK: 627 // Nothing to do here 628 return HANDLED; 629 case DISCONNECT_DOCK: 630 // Nothing to do here 631 return HANDLED; 632 default: 633 return NOT_HANDLED; 634 } 635 } 636 } 637 638 class ActiveBluetoothRoute extends BluetoothRoute { 639 @Override getName()640 public String getName() { 641 return ACTIVE_BLUETOOTH_ROUTE_NAME; 642 } 643 644 @Override isActive()645 public boolean isActive() { 646 return true; 647 } 648 649 @Override enter()650 public void enter() { 651 super.enter(); 652 setSpeakerphoneOn(false); 653 setBluetoothOn(true); 654 CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_BLUETOOTH, 655 mAvailableRoutes); 656 setSystemAudioState(newState, true); 657 updateInternalCallAudioState(); 658 } 659 660 @Override updateSystemAudioState()661 public void updateSystemAudioState() { 662 updateInternalCallAudioState(); 663 setSystemAudioState(mCurrentCallAudioState); 664 } 665 666 @Override processMessage(Message msg)667 public boolean processMessage(Message msg) { 668 if (super.processMessage(msg) == HANDLED) { 669 return HANDLED; 670 } 671 switch (msg.what) { 672 case USER_SWITCH_EARPIECE: 673 mHasUserExplicitlyLeftBluetooth = true; 674 // fall through 675 case SWITCH_EARPIECE: 676 if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) { 677 transitionTo(mActiveEarpieceRoute); 678 } else { 679 Log.w(this, "Ignoring switch to earpiece command. Not available."); 680 } 681 return HANDLED; 682 case SWITCH_BLUETOOTH: 683 case USER_SWITCH_BLUETOOTH: 684 // Nothing to do 685 return HANDLED; 686 case USER_SWITCH_HEADSET: 687 mHasUserExplicitlyLeftBluetooth = true; 688 // fall through 689 case SWITCH_HEADSET: 690 if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) { 691 transitionTo(mActiveHeadsetRoute); 692 } else { 693 Log.w(this, "Ignoring switch to headset command. Not available."); 694 } 695 return HANDLED; 696 case USER_SWITCH_SPEAKER: 697 mHasUserExplicitlyLeftBluetooth = true; 698 // fall through 699 case SWITCH_SPEAKER: 700 transitionTo(mActiveSpeakerRoute); 701 return HANDLED; 702 case SWITCH_FOCUS: 703 if (msg.arg1 == NO_FOCUS) { 704 reinitialize(); 705 } else if (msg.arg1 == RINGING_FOCUS) { 706 transitionTo(mRingingBluetoothRoute); 707 } 708 return HANDLED; 709 case BT_AUDIO_DISCONNECT: 710 sendInternalMessage(SWITCH_BASELINE_ROUTE, NO_INCLUDE_BLUETOOTH_IN_BASELINE); 711 return HANDLED; 712 default: 713 return NOT_HANDLED; 714 } 715 } 716 } 717 718 class RingingBluetoothRoute extends BluetoothRoute { 719 @Override getName()720 public String getName() { 721 return RINGING_BLUETOOTH_ROUTE_NAME; 722 } 723 724 @Override isActive()725 public boolean isActive() { 726 return false; 727 } 728 729 @Override enter()730 public void enter() { 731 super.enter(); 732 setSpeakerphoneOn(false); 733 // Do not enable SCO audio here, since RING is being sent to the headset. 734 CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_BLUETOOTH, 735 mAvailableRoutes); 736 setSystemAudioState(newState); 737 updateInternalCallAudioState(); 738 } 739 740 @Override updateSystemAudioState()741 public void updateSystemAudioState() { 742 updateInternalCallAudioState(); 743 setSystemAudioState(mCurrentCallAudioState); 744 } 745 746 @Override processMessage(Message msg)747 public boolean processMessage(Message msg) { 748 if (super.processMessage(msg) == HANDLED) { 749 return HANDLED; 750 } 751 switch (msg.what) { 752 case USER_SWITCH_EARPIECE: 753 mHasUserExplicitlyLeftBluetooth = true; 754 // fall through 755 case SWITCH_EARPIECE: 756 if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) { 757 transitionTo(mActiveEarpieceRoute); 758 } else { 759 Log.w(this, "Ignoring switch to earpiece command. Not available."); 760 } 761 return HANDLED; 762 case SWITCH_BLUETOOTH: 763 case USER_SWITCH_BLUETOOTH: 764 // Nothing to do 765 return HANDLED; 766 case USER_SWITCH_HEADSET: 767 mHasUserExplicitlyLeftBluetooth = true; 768 // fall through 769 case SWITCH_HEADSET: 770 if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) { 771 transitionTo(mActiveHeadsetRoute); 772 } else { 773 Log.w(this, "Ignoring switch to headset command. Not available."); 774 } 775 return HANDLED; 776 case USER_SWITCH_SPEAKER: 777 mHasUserExplicitlyLeftBluetooth = true; 778 // fall through 779 case SWITCH_SPEAKER: 780 transitionTo(mActiveSpeakerRoute); 781 return HANDLED; 782 case SWITCH_FOCUS: 783 if (msg.arg1 == NO_FOCUS) { 784 reinitialize(); 785 } else if (msg.arg1 == ACTIVE_FOCUS) { 786 transitionTo(mActiveBluetoothRoute); 787 } 788 return HANDLED; 789 case BT_AUDIO_DISCONNECT: 790 // BT SCO might be connected when in-band ringing is enabled 791 sendInternalMessage(SWITCH_BASELINE_ROUTE, NO_INCLUDE_BLUETOOTH_IN_BASELINE); 792 return HANDLED; 793 default: 794 return NOT_HANDLED; 795 } 796 } 797 } 798 799 class QuiescentBluetoothRoute extends BluetoothRoute { 800 @Override getName()801 public String getName() { 802 return QUIESCENT_BLUETOOTH_ROUTE_NAME; 803 } 804 805 @Override isActive()806 public boolean isActive() { 807 return false; 808 } 809 810 @Override enter()811 public void enter() { 812 super.enter(); 813 mHasUserExplicitlyLeftBluetooth = false; 814 updateInternalCallAudioState(); 815 setBluetoothOn(false); 816 } 817 818 @Override updateSystemAudioState()819 public void updateSystemAudioState() { 820 updateInternalCallAudioState(); 821 } 822 823 @Override processMessage(Message msg)824 public boolean processMessage(Message msg) { 825 if (super.processMessage(msg) == HANDLED) { 826 return HANDLED; 827 } 828 switch (msg.what) { 829 case SWITCH_EARPIECE: 830 case USER_SWITCH_EARPIECE: 831 if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) { 832 transitionTo(mQuiescentEarpieceRoute); 833 } else { 834 Log.w(this, "Ignoring switch to earpiece command. Not available."); 835 } 836 return HANDLED; 837 case SWITCH_BLUETOOTH: 838 case USER_SWITCH_BLUETOOTH: 839 // Nothing to do 840 return HANDLED; 841 case SWITCH_HEADSET: 842 case USER_SWITCH_HEADSET: 843 if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) { 844 transitionTo(mQuiescentHeadsetRoute); 845 } else { 846 Log.w(this, "Ignoring switch to headset command. Not available."); 847 } 848 return HANDLED; 849 case SWITCH_SPEAKER: 850 case USER_SWITCH_SPEAKER: 851 transitionTo(mQuiescentSpeakerRoute); 852 return HANDLED; 853 case SWITCH_FOCUS: 854 if (msg.arg1 == ACTIVE_FOCUS) { 855 transitionTo(mActiveBluetoothRoute); 856 } else if (msg.arg1 == RINGING_FOCUS) { 857 transitionTo(mRingingBluetoothRoute); 858 } 859 return HANDLED; 860 case BT_AUDIO_DISCONNECT: 861 // Ignore this -- audio disconnecting while quiescent should not cause a 862 // route switch, since the device is still connected. 863 return HANDLED; 864 default: 865 return NOT_HANDLED; 866 } 867 } 868 } 869 870 abstract class BluetoothRoute extends AudioState { 871 @Override getRouteCode()872 public int getRouteCode() { 873 return CallAudioState.ROUTE_BLUETOOTH; 874 } 875 876 @Override processMessage(Message msg)877 public boolean processMessage(Message msg) { 878 if (super.processMessage(msg) == HANDLED) { 879 return HANDLED; 880 } 881 switch (msg.what) { 882 case CONNECT_WIRED_HEADSET: 883 sendInternalMessage(SWITCH_HEADSET); 884 return HANDLED; 885 case CONNECT_BLUETOOTH: 886 // We can't tell when a change in bluetooth state corresponds to an 887 // actual connection or disconnection, so we'll just ignore it if we're already 888 // in the bluetooth route. 889 return HANDLED; 890 case DISCONNECT_BLUETOOTH: 891 sendInternalMessage(SWITCH_BASELINE_ROUTE, NO_INCLUDE_BLUETOOTH_IN_BASELINE); 892 mWasOnSpeaker = false; 893 return HANDLED; 894 case DISCONNECT_WIRED_HEADSET: 895 updateSystemAudioState(); 896 // No change in audio route required 897 return HANDLED; 898 case CONNECT_DOCK: 899 // Nothing to do here 900 return HANDLED; 901 case DISCONNECT_DOCK: 902 // Nothing to do here 903 return HANDLED; 904 default: 905 return NOT_HANDLED; 906 } 907 } 908 } 909 910 class ActiveSpeakerRoute extends SpeakerRoute { 911 @Override getName()912 public String getName() { 913 return ACTIVE_SPEAKER_ROUTE_NAME; 914 } 915 916 @Override isActive()917 public boolean isActive() { 918 return true; 919 } 920 921 @Override enter()922 public void enter() { 923 super.enter(); 924 mWasOnSpeaker = true; 925 setSpeakerphoneOn(true); 926 setBluetoothOn(false); 927 CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_SPEAKER, 928 mAvailableRoutes); 929 setSystemAudioState(newState); 930 updateInternalCallAudioState(); 931 } 932 933 @Override updateSystemAudioState()934 public void updateSystemAudioState() { 935 updateInternalCallAudioState(); 936 setSystemAudioState(mCurrentCallAudioState); 937 } 938 939 @Override processMessage(Message msg)940 public boolean processMessage(Message msg) { 941 if (super.processMessage(msg) == HANDLED) { 942 return HANDLED; 943 } 944 switch(msg.what) { 945 case USER_SWITCH_EARPIECE: 946 mWasOnSpeaker = false; 947 // fall through 948 case SWITCH_EARPIECE: 949 if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) { 950 transitionTo(mActiveEarpieceRoute); 951 } else { 952 Log.w(this, "Ignoring switch to earpiece command. Not available."); 953 } 954 return HANDLED; 955 case USER_SWITCH_BLUETOOTH: 956 mWasOnSpeaker = false; 957 // fall through 958 case SWITCH_BLUETOOTH: 959 if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) { 960 transitionTo(mAudioFocusType == ACTIVE_FOCUS ? 961 mActiveBluetoothRoute : mRingingBluetoothRoute); 962 } else { 963 Log.w(this, "Ignoring switch to bluetooth command. Not available."); 964 } 965 return HANDLED; 966 case USER_SWITCH_HEADSET: 967 mWasOnSpeaker = false; 968 // fall through 969 case SWITCH_HEADSET: 970 if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) { 971 transitionTo(mActiveHeadsetRoute); 972 } else { 973 Log.w(this, "Ignoring switch to headset command. Not available."); 974 } 975 return HANDLED; 976 case SWITCH_SPEAKER: 977 case USER_SWITCH_SPEAKER: 978 // Nothing to do 979 return HANDLED; 980 case SWITCH_FOCUS: 981 if (msg.arg1 == NO_FOCUS) { 982 reinitialize(); 983 } 984 return HANDLED; 985 default: 986 return NOT_HANDLED; 987 } 988 } 989 } 990 991 class QuiescentSpeakerRoute extends SpeakerRoute { 992 @Override getName()993 public String getName() { 994 return QUIESCENT_SPEAKER_ROUTE_NAME; 995 } 996 997 @Override isActive()998 public boolean isActive() { 999 return false; 1000 } 1001 1002 @Override enter()1003 public void enter() { 1004 super.enter(); 1005 mHasUserExplicitlyLeftBluetooth = false; 1006 // Omit setting mWasOnSpeaker to true here, since this does not reflect a call 1007 // actually being on speakerphone. 1008 updateInternalCallAudioState(); 1009 } 1010 1011 @Override updateSystemAudioState()1012 public void updateSystemAudioState() { 1013 updateInternalCallAudioState(); 1014 } 1015 1016 @Override processMessage(Message msg)1017 public boolean processMessage(Message msg) { 1018 if (super.processMessage(msg) == HANDLED) { 1019 return HANDLED; 1020 } 1021 switch(msg.what) { 1022 case SWITCH_EARPIECE: 1023 case USER_SWITCH_EARPIECE: 1024 if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) { 1025 transitionTo(mQuiescentEarpieceRoute); 1026 } else { 1027 Log.w(this, "Ignoring switch to earpiece command. Not available."); 1028 } 1029 return HANDLED; 1030 case SWITCH_BLUETOOTH: 1031 case USER_SWITCH_BLUETOOTH: 1032 if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) { 1033 transitionTo(mQuiescentBluetoothRoute); 1034 } else { 1035 Log.w(this, "Ignoring switch to bluetooth command. Not available."); 1036 } 1037 return HANDLED; 1038 case SWITCH_HEADSET: 1039 case USER_SWITCH_HEADSET: 1040 if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) { 1041 transitionTo(mQuiescentHeadsetRoute); 1042 } else { 1043 Log.w(this, "Ignoring switch to headset command. Not available."); 1044 } 1045 return HANDLED; 1046 case SWITCH_SPEAKER: 1047 case USER_SWITCH_SPEAKER: 1048 // Nothing to do 1049 return HANDLED; 1050 case SWITCH_FOCUS: 1051 if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) { 1052 transitionTo(mActiveSpeakerRoute); 1053 } 1054 return HANDLED; 1055 default: 1056 return NOT_HANDLED; 1057 } 1058 } 1059 } 1060 1061 abstract class SpeakerRoute extends AudioState { 1062 @Override getRouteCode()1063 public int getRouteCode() { 1064 return CallAudioState.ROUTE_SPEAKER; 1065 } 1066 1067 @Override processMessage(Message msg)1068 public boolean processMessage(Message msg) { 1069 if (super.processMessage(msg) == HANDLED) { 1070 return HANDLED; 1071 } 1072 switch (msg.what) { 1073 case CONNECT_WIRED_HEADSET: 1074 sendInternalMessage(SWITCH_HEADSET); 1075 return HANDLED; 1076 case CONNECT_BLUETOOTH: 1077 if (!mHasUserExplicitlyLeftBluetooth) { 1078 sendInternalMessage(SWITCH_BLUETOOTH); 1079 } else { 1080 Log.i(this, "Not switching to BT route from speaker because user has " + 1081 "explicitly disconnected."); 1082 updateSystemAudioState(); 1083 } 1084 return HANDLED; 1085 case DISCONNECT_BLUETOOTH: 1086 updateSystemAudioState(); 1087 // No change in audio route required 1088 return HANDLED; 1089 case DISCONNECT_WIRED_HEADSET: 1090 updateSystemAudioState(); 1091 // No change in audio route required 1092 return HANDLED; 1093 case BT_AUDIO_DISCONNECT: 1094 // This may be sent as a confirmation by the BT stack after switch off BT. 1095 return HANDLED; 1096 case CONNECT_DOCK: 1097 // Nothing to do here 1098 return HANDLED; 1099 case DISCONNECT_DOCK: 1100 sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE); 1101 return HANDLED; 1102 default: 1103 return NOT_HANDLED; 1104 } 1105 } 1106 } 1107 1108 private final ActiveEarpieceRoute mActiveEarpieceRoute = new ActiveEarpieceRoute(); 1109 private final ActiveHeadsetRoute mActiveHeadsetRoute = new ActiveHeadsetRoute(); 1110 private final ActiveBluetoothRoute mActiveBluetoothRoute = new ActiveBluetoothRoute(); 1111 private final ActiveSpeakerRoute mActiveSpeakerRoute = new ActiveSpeakerRoute(); 1112 private final RingingBluetoothRoute mRingingBluetoothRoute = new RingingBluetoothRoute(); 1113 private final QuiescentEarpieceRoute mQuiescentEarpieceRoute = new QuiescentEarpieceRoute(); 1114 private final QuiescentHeadsetRoute mQuiescentHeadsetRoute = new QuiescentHeadsetRoute(); 1115 private final QuiescentBluetoothRoute mQuiescentBluetoothRoute = new QuiescentBluetoothRoute(); 1116 private final QuiescentSpeakerRoute mQuiescentSpeakerRoute = new QuiescentSpeakerRoute(); 1117 1118 /** 1119 * A few pieces of hidden state. Used to avoid exponential explosion of number of explicit 1120 * states 1121 */ 1122 private int mDeviceSupportedRoutes; 1123 private int mAvailableRoutes; 1124 private int mAudioFocusType; 1125 private boolean mWasOnSpeaker; 1126 private boolean mIsMuted; 1127 1128 private final Context mContext; 1129 private final CallsManager mCallsManager; 1130 private final AudioManager mAudioManager; 1131 private final BluetoothRouteManager mBluetoothRouteManager; 1132 private final WiredHeadsetManager mWiredHeadsetManager; 1133 private final StatusBarNotifier mStatusBarNotifier; 1134 private final CallAudioManager.AudioServiceFactory mAudioServiceFactory; 1135 private final boolean mDoesDeviceSupportEarpieceRoute; 1136 private final TelecomSystem.SyncRoot mLock; 1137 private boolean mHasUserExplicitlyLeftBluetooth = false; 1138 1139 private HashMap<String, Integer> mStateNameToRouteCode; 1140 private HashMap<Integer, AudioState> mRouteCodeToQuiescentState; 1141 1142 // CallAudioState is used as an interface to communicate with many other system components. 1143 // No internal state transitions should depend on this variable. 1144 private CallAudioState mCurrentCallAudioState; 1145 private CallAudioState mLastKnownCallAudioState; 1146 CallAudioRouteStateMachine( Context context, CallsManager callsManager, BluetoothRouteManager bluetoothManager, WiredHeadsetManager wiredHeadsetManager, StatusBarNotifier statusBarNotifier, CallAudioManager.AudioServiceFactory audioServiceFactory, boolean doesDeviceSupportEarpieceRoute)1147 public CallAudioRouteStateMachine( 1148 Context context, 1149 CallsManager callsManager, 1150 BluetoothRouteManager bluetoothManager, 1151 WiredHeadsetManager wiredHeadsetManager, 1152 StatusBarNotifier statusBarNotifier, 1153 CallAudioManager.AudioServiceFactory audioServiceFactory, 1154 boolean doesDeviceSupportEarpieceRoute) { 1155 super(NAME); 1156 addState(mActiveEarpieceRoute); 1157 addState(mActiveHeadsetRoute); 1158 addState(mActiveBluetoothRoute); 1159 addState(mActiveSpeakerRoute); 1160 addState(mRingingBluetoothRoute); 1161 addState(mQuiescentEarpieceRoute); 1162 addState(mQuiescentHeadsetRoute); 1163 addState(mQuiescentBluetoothRoute); 1164 addState(mQuiescentSpeakerRoute); 1165 1166 mContext = context; 1167 mCallsManager = callsManager; 1168 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 1169 mBluetoothRouteManager = bluetoothManager; 1170 mWiredHeadsetManager = wiredHeadsetManager; 1171 mStatusBarNotifier = statusBarNotifier; 1172 mAudioServiceFactory = audioServiceFactory; 1173 mDoesDeviceSupportEarpieceRoute = doesDeviceSupportEarpieceRoute; 1174 mLock = callsManager.getLock(); 1175 1176 mStateNameToRouteCode = new HashMap<>(8); 1177 mStateNameToRouteCode.put(mQuiescentEarpieceRoute.getName(), ROUTE_EARPIECE); 1178 mStateNameToRouteCode.put(mQuiescentBluetoothRoute.getName(), ROUTE_BLUETOOTH); 1179 mStateNameToRouteCode.put(mQuiescentHeadsetRoute.getName(), ROUTE_WIRED_HEADSET); 1180 mStateNameToRouteCode.put(mQuiescentSpeakerRoute.getName(), ROUTE_SPEAKER); 1181 mStateNameToRouteCode.put(mRingingBluetoothRoute.getName(), ROUTE_BLUETOOTH); 1182 mStateNameToRouteCode.put(mActiveEarpieceRoute.getName(), ROUTE_EARPIECE); 1183 mStateNameToRouteCode.put(mActiveBluetoothRoute.getName(), ROUTE_BLUETOOTH); 1184 mStateNameToRouteCode.put(mActiveHeadsetRoute.getName(), ROUTE_WIRED_HEADSET); 1185 mStateNameToRouteCode.put(mActiveSpeakerRoute.getName(), ROUTE_SPEAKER); 1186 1187 mRouteCodeToQuiescentState = new HashMap<>(4); 1188 mRouteCodeToQuiescentState.put(ROUTE_EARPIECE, mQuiescentEarpieceRoute); 1189 mRouteCodeToQuiescentState.put(ROUTE_BLUETOOTH, mQuiescentBluetoothRoute); 1190 mRouteCodeToQuiescentState.put(ROUTE_SPEAKER, mQuiescentSpeakerRoute); 1191 mRouteCodeToQuiescentState.put(ROUTE_WIRED_HEADSET, mQuiescentHeadsetRoute); 1192 } 1193 1194 /** 1195 * Initializes the state machine with info on initial audio route, supported audio routes, 1196 * and mute status. 1197 */ initialize()1198 public void initialize() { 1199 CallAudioState initState = getInitialAudioState(); 1200 initialize(initState); 1201 } 1202 initialize(CallAudioState initState)1203 public void initialize(CallAudioState initState) { 1204 if ((initState.getRoute() & getCurrentCallSupportedRoutes()) == 0) { 1205 Log.e(this, new IllegalArgumentException(), "Route %d specified when supported call" + 1206 " routes are: %d", initState.getRoute(), getCurrentCallSupportedRoutes()); 1207 } 1208 1209 mCurrentCallAudioState = initState; 1210 mLastKnownCallAudioState = initState; 1211 mDeviceSupportedRoutes = initState.getSupportedRouteMask(); 1212 mAvailableRoutes = mDeviceSupportedRoutes & getCurrentCallSupportedRoutes(); 1213 mIsMuted = initState.isMuted(); 1214 mWasOnSpeaker = false; 1215 1216 mStatusBarNotifier.notifyMute(initState.isMuted()); 1217 mStatusBarNotifier.notifySpeakerphone(initState.getRoute() == CallAudioState.ROUTE_SPEAKER); 1218 setInitialState(mRouteCodeToQuiescentState.get(initState.getRoute())); 1219 start(); 1220 } 1221 1222 /** 1223 * Getter for the current CallAudioState object that the state machine is keeping track of. 1224 * Used for compatibility purposes. 1225 */ getCurrentCallAudioState()1226 public CallAudioState getCurrentCallAudioState() { 1227 return mCurrentCallAudioState; 1228 } 1229 sendMessageWithSessionInfo(int message, int arg)1230 public void sendMessageWithSessionInfo(int message, int arg) { 1231 sendMessage(message, arg, 0, Log.createSubsession()); 1232 } 1233 sendMessageWithSessionInfo(int message)1234 public void sendMessageWithSessionInfo(int message) { 1235 sendMessage(message, 0, 0, Log.createSubsession()); 1236 } 1237 1238 /** 1239 * This is for state-independent changes in audio route (i.e. muting or runnables) 1240 * @param msg that couldn't be handled. 1241 */ 1242 @Override unhandledMessage(Message msg)1243 protected void unhandledMessage(Message msg) { 1244 CallAudioState newCallAudioState; 1245 switch (msg.what) { 1246 case MUTE_ON: 1247 setMuteOn(true); 1248 newCallAudioState = new CallAudioState(mIsMuted, 1249 mCurrentCallAudioState.getRoute(), 1250 mAvailableRoutes); 1251 setSystemAudioState(newCallAudioState); 1252 updateInternalCallAudioState(); 1253 return; 1254 case MUTE_OFF: 1255 setMuteOn(false); 1256 newCallAudioState = new CallAudioState(mIsMuted, 1257 mCurrentCallAudioState.getRoute(), 1258 mAvailableRoutes); 1259 setSystemAudioState(newCallAudioState); 1260 updateInternalCallAudioState(); 1261 return; 1262 case TOGGLE_MUTE: 1263 if (mIsMuted) { 1264 sendInternalMessage(MUTE_OFF); 1265 } else { 1266 sendInternalMessage(MUTE_ON); 1267 } 1268 return; 1269 case UPDATE_SYSTEM_AUDIO_ROUTE: 1270 updateRouteForForegroundCall(); 1271 resendSystemAudioState(); 1272 return; 1273 case RUN_RUNNABLE: 1274 java.lang.Runnable r = (java.lang.Runnable) msg.obj; 1275 r.run(); 1276 return; 1277 default: 1278 Log.e(this, new IllegalStateException(), 1279 "Unexpected message code %d", msg.what); 1280 } 1281 } 1282 quitStateMachine()1283 public void quitStateMachine() { 1284 quitNow(); 1285 } 1286 dumpPendingMessages(IndentingPrintWriter pw)1287 public void dumpPendingMessages(IndentingPrintWriter pw) { 1288 getHandler().getLooper().dump(pw::println, ""); 1289 } 1290 isHfpDeviceAvailable()1291 public boolean isHfpDeviceAvailable() { 1292 return mBluetoothRouteManager.isBluetoothAvailable(); 1293 } 1294 setSpeakerphoneOn(boolean on)1295 private void setSpeakerphoneOn(boolean on) { 1296 if (mAudioManager.isSpeakerphoneOn() != on) { 1297 Log.i(this, "turning speaker phone %s", on); 1298 mAudioManager.setSpeakerphoneOn(on); 1299 mStatusBarNotifier.notifySpeakerphone(on); 1300 } 1301 } 1302 setBluetoothOn(boolean on)1303 private void setBluetoothOn(boolean on) { 1304 if (mBluetoothRouteManager.isBluetoothAvailable()) { 1305 if (on != mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()) { 1306 Log.i(this, "connecting bluetooth %s", on); 1307 if (on) { 1308 mBluetoothRouteManager.connectBluetoothAudio(null /*TODO: add real address*/); 1309 } else { 1310 mBluetoothRouteManager.disconnectBluetoothAudio(); 1311 } 1312 } 1313 } 1314 } 1315 setMuteOn(boolean mute)1316 private void setMuteOn(boolean mute) { 1317 mIsMuted = mute; 1318 Log.addEvent(mCallsManager.getForegroundCall(), mute ? 1319 LogUtils.Events.MUTE : LogUtils.Events.UNMUTE); 1320 if (mute != mAudioManager.isMicrophoneMute() && isInActiveState()) { 1321 IAudioService audio = mAudioServiceFactory.getAudioService(); 1322 Log.i(this, "changing microphone mute state to: %b [serviceIsNull=%b]", 1323 mute, audio == null); 1324 if (audio != null) { 1325 try { 1326 // We use the audio service directly here so that we can specify 1327 // the current user. Telecom runs in the system_server process which 1328 // may run as a separate user from the foreground user. If we 1329 // used AudioManager directly, we would change mute for the system's 1330 // user and not the current foreground, which we want to avoid. 1331 audio.setMicrophoneMute( 1332 mute, mContext.getOpPackageName(), getCurrentUserId()); 1333 mStatusBarNotifier.notifyMute(mute); 1334 1335 } catch (RemoteException e) { 1336 Log.e(this, e, "Remote exception while toggling mute."); 1337 } 1338 // TODO: Check microphone state after attempting to set to ensure that 1339 // our state corroborates AudioManager's state. 1340 } 1341 } 1342 } 1343 1344 /** 1345 * Updates the CallAudioState object from current internal state. The result is used for 1346 * external communication only. 1347 */ updateInternalCallAudioState()1348 private void updateInternalCallAudioState() { 1349 IState currentState = getCurrentState(); 1350 if (currentState == null) { 1351 Log.e(this, new IllegalStateException(), "Current state should never be null" + 1352 " when updateInternalCallAudioState is called."); 1353 mCurrentCallAudioState = new CallAudioState( 1354 mIsMuted, mCurrentCallAudioState.getRoute(), mAvailableRoutes); 1355 return; 1356 } 1357 int currentRoute = mStateNameToRouteCode.get(currentState.getName()); 1358 mCurrentCallAudioState = new CallAudioState(mIsMuted, currentRoute, mAvailableRoutes); 1359 } 1360 setSystemAudioState(CallAudioState newCallAudioState)1361 private void setSystemAudioState(CallAudioState newCallAudioState) { 1362 setSystemAudioState(newCallAudioState, false); 1363 } 1364 resendSystemAudioState()1365 private void resendSystemAudioState() { 1366 setSystemAudioState(mLastKnownCallAudioState, true); 1367 } 1368 setSystemAudioState(CallAudioState newCallAudioState, boolean force)1369 private void setSystemAudioState(CallAudioState newCallAudioState, boolean force) { 1370 synchronized (mLock) { 1371 Log.i(this, "setSystemAudioState: changing from %s to %s", mLastKnownCallAudioState, 1372 newCallAudioState); 1373 if (force || !newCallAudioState.equals(mLastKnownCallAudioState)) { 1374 1375 mCallsManager.onCallAudioStateChanged(mLastKnownCallAudioState, newCallAudioState); 1376 updateAudioForForegroundCall(newCallAudioState); 1377 mLastKnownCallAudioState = newCallAudioState; 1378 } 1379 } 1380 } 1381 updateAudioForForegroundCall(CallAudioState newCallAudioState)1382 private void updateAudioForForegroundCall(CallAudioState newCallAudioState) { 1383 Call call = mCallsManager.getForegroundCall(); 1384 if (call != null && call.getConnectionService() != null) { 1385 call.getConnectionService().onCallAudioStateChanged(call, newCallAudioState); 1386 } 1387 } 1388 calculateSupportedRoutes()1389 private int calculateSupportedRoutes() { 1390 int routeMask = CallAudioState.ROUTE_SPEAKER; 1391 1392 if (mWiredHeadsetManager.isPluggedIn()) { 1393 routeMask |= CallAudioState.ROUTE_WIRED_HEADSET; 1394 } else if (mDoesDeviceSupportEarpieceRoute){ 1395 routeMask |= CallAudioState.ROUTE_EARPIECE; 1396 } 1397 1398 if (mBluetoothRouteManager.isBluetoothAvailable()) { 1399 routeMask |= CallAudioState.ROUTE_BLUETOOTH; 1400 } 1401 1402 return routeMask; 1403 } 1404 sendInternalMessage(int messageCode)1405 private void sendInternalMessage(int messageCode) { 1406 sendInternalMessage(messageCode, 0); 1407 } 1408 sendInternalMessage(int messageCode, int arg1)1409 private void sendInternalMessage(int messageCode, int arg1) { 1410 // Internal messages are messages which the state machine sends to itself in the 1411 // course of processing externally-sourced messages. We want to send these messages at 1412 // the front of the queue in order to make actions appear atomic to the user and to 1413 // prevent scenarios such as these: 1414 // 1. State machine handler thread is suspended for some reason. 1415 // 2. Headset gets connected (sends CONNECT_HEADSET). 1416 // 3. User switches to speakerphone in the UI (sends SWITCH_SPEAKER). 1417 // 4. State machine handler is un-suspended. 1418 // 5. State machine handler processes the CONNECT_HEADSET message and sends 1419 // SWITCH_HEADSET at end of queue. 1420 // 6. State machine handler processes SWITCH_SPEAKER. 1421 // 7. State machine handler processes SWITCH_HEADSET. 1422 Session subsession = Log.createSubsession(); 1423 if(subsession != null) { 1424 sendMessageAtFrontOfQueue(messageCode, arg1, 0, subsession); 1425 } else { 1426 sendMessageAtFrontOfQueue(messageCode, arg1); 1427 } 1428 } 1429 getInitialAudioState()1430 private CallAudioState getInitialAudioState() { 1431 int supportedRouteMask = calculateSupportedRoutes() & getCurrentCallSupportedRoutes(); 1432 final int route; 1433 1434 if ((supportedRouteMask & ROUTE_BLUETOOTH) != 0) { 1435 route = ROUTE_BLUETOOTH; 1436 } else if ((supportedRouteMask & ROUTE_WIRED_HEADSET) != 0) { 1437 route = ROUTE_WIRED_HEADSET; 1438 } else if ((supportedRouteMask & ROUTE_EARPIECE) != 0) { 1439 route = ROUTE_EARPIECE; 1440 } else { 1441 route = ROUTE_SPEAKER; 1442 } 1443 1444 return new CallAudioState(false, route, supportedRouteMask); 1445 } 1446 getCurrentUserId()1447 private int getCurrentUserId() { 1448 final long ident = Binder.clearCallingIdentity(); 1449 try { 1450 UserInfo currentUser = ActivityManager.getService().getCurrentUser(); 1451 return currentUser.id; 1452 } catch (RemoteException e) { 1453 // Activity manager not running, nothing we can do assume user 0. 1454 } finally { 1455 Binder.restoreCallingIdentity(ident); 1456 } 1457 return UserHandle.USER_OWNER; 1458 } 1459 isInActiveState()1460 private boolean isInActiveState() { 1461 AudioState currentState = (AudioState) getCurrentState(); 1462 if (currentState == null) { 1463 Log.w(this, "Current state is null, assuming inactive state"); 1464 return false; 1465 } 1466 return currentState.isActive(); 1467 } 1468 doesDeviceSupportEarpieceRoute()1469 public static boolean doesDeviceSupportEarpieceRoute() { 1470 String[] characteristics = SystemProperties.get("ro.build.characteristics").split(","); 1471 for (String characteristic : characteristics) { 1472 if ("watch".equals(characteristic)) { 1473 return false; 1474 } 1475 } 1476 return true; 1477 } 1478 calculateBaselineRouteMessage(boolean isExplicitUserRequest, boolean includeBluetooth)1479 private int calculateBaselineRouteMessage(boolean isExplicitUserRequest, 1480 boolean includeBluetooth) { 1481 boolean isSkipEarpiece = false; 1482 if (!isExplicitUserRequest) { 1483 synchronized (mLock) { 1484 // Check video calls to skip earpiece since the baseline for video 1485 // calls should be the speakerphone route 1486 isSkipEarpiece = mCallsManager.hasVideoCall(); 1487 } 1488 } 1489 if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0 1490 && !mHasUserExplicitlyLeftBluetooth 1491 && includeBluetooth) { 1492 return isExplicitUserRequest ? USER_SWITCH_BLUETOOTH : SWITCH_BLUETOOTH; 1493 } else if ((mAvailableRoutes & ROUTE_EARPIECE) != 0 && !isSkipEarpiece) { 1494 return isExplicitUserRequest ? USER_SWITCH_EARPIECE : SWITCH_EARPIECE; 1495 } else if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) { 1496 return isExplicitUserRequest ? USER_SWITCH_HEADSET : SWITCH_HEADSET; 1497 } else { 1498 return isExplicitUserRequest ? USER_SWITCH_SPEAKER : SWITCH_SPEAKER; 1499 } 1500 } 1501 reinitialize()1502 private void reinitialize() { 1503 CallAudioState initState = getInitialAudioState(); 1504 mDeviceSupportedRoutes = initState.getSupportedRouteMask(); 1505 mAvailableRoutes = mDeviceSupportedRoutes & getCurrentCallSupportedRoutes(); 1506 mIsMuted = initState.isMuted(); 1507 setMuteOn(mIsMuted); 1508 mWasOnSpeaker = false; 1509 mHasUserExplicitlyLeftBluetooth = false; 1510 mLastKnownCallAudioState = initState; 1511 transitionTo(mRouteCodeToQuiescentState.get(initState.getRoute())); 1512 } 1513 updateRouteForForegroundCall()1514 private void updateRouteForForegroundCall() { 1515 mAvailableRoutes = mDeviceSupportedRoutes & getCurrentCallSupportedRoutes(); 1516 1517 CallAudioState currentState = getCurrentCallAudioState(); 1518 1519 // Move to baseline route in the case the current route is no longer available. 1520 if ((mAvailableRoutes & currentState.getRoute()) == 0) { 1521 sendInternalMessage(calculateBaselineRouteMessage(false, true)); 1522 } 1523 } 1524 getCurrentCallSupportedRoutes()1525 private int getCurrentCallSupportedRoutes() { 1526 int supportedRoutes = CallAudioState.ROUTE_ALL; 1527 1528 if (mCallsManager.getForegroundCall() != null) { 1529 supportedRoutes &= mCallsManager.getForegroundCall().getSupportedAudioRoutes(); 1530 } 1531 1532 return supportedRoutes; 1533 } 1534 modifyRoutes(int base, int remove, int add, boolean considerCurrentCall)1535 private int modifyRoutes(int base, int remove, int add, boolean considerCurrentCall) { 1536 base &= ~remove; 1537 1538 if (considerCurrentCall) { 1539 add &= getCurrentCallSupportedRoutes(); 1540 } 1541 1542 base |= add; 1543 1544 return base; 1545 } 1546 } 1547