CallAudioRouteStateMachine.java revision ef15aea6ef54e3a8b04d534b14266b7a9009f085
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 17package com.android.server.telecom; 18 19 20import android.app.ActivityManagerNative; 21import android.content.Context; 22import android.content.pm.UserInfo; 23import android.media.AudioManager; 24import android.media.IAudioService; 25import android.os.Binder; 26import android.os.Message; 27import android.os.RemoteException; 28import android.os.UserHandle; 29import android.telecom.CallAudioState; 30import android.util.SparseArray; 31 32import com.android.internal.util.IState; 33import com.android.internal.util.State; 34import com.android.internal.util.StateMachine; 35 36import java.util.HashMap; 37 38/** 39 * This class describes the available routes of a call as a state machine. 40 * Transitions are caused solely by the commands sent as messages. Possible values for msg.what 41 * are defined as event constants in this file. 42 * 43 * The eight states are all instances of the abstract base class, {@link AudioState}. Each state 44 * is a combination of one of the four audio routes (earpiece, wired headset, bluetooth, and 45 * speakerphone) and audio focus status (active or quiescent). 46 * 47 * Messages are processed first by the processMessage method in the base class, AudioState. 48 * Any messages not completely handled by AudioState are further processed by the same method in 49 * the route-specific abstract classes: {@link EarpieceRoute}, {@link HeadsetRoute}, 50 * {@link BluetoothRoute}, and {@link SpeakerRoute}. Finally, messages that are not handled at 51 * this level are then processed by the classes corresponding to the state instances themselves. 52 * 53 * There are several variables carrying additional state. These include: 54 * mAvailableRoutes: A bitmask describing which audio routes are available 55 * mWasOnSpeaker: A boolean indicating whether we should switch to speakerphone after disconnecting 56 * from a wired headset 57 * mIsMuted: a boolean indicating whether the audio is muted 58 */ 59public class CallAudioRouteStateMachine extends StateMachine { 60 /** Direct the audio stream through the device's earpiece. */ 61 public static final int ROUTE_EARPIECE = CallAudioState.ROUTE_EARPIECE; 62 63 /** Direct the audio stream through Bluetooth. */ 64 public static final int ROUTE_BLUETOOTH = CallAudioState.ROUTE_BLUETOOTH; 65 66 /** Direct the audio stream through a wired headset. */ 67 public static final int ROUTE_WIRED_HEADSET = CallAudioState.ROUTE_WIRED_HEADSET; 68 69 /** Direct the audio stream through the device's speakerphone. */ 70 public static final int ROUTE_SPEAKER = CallAudioState.ROUTE_SPEAKER; 71 72 /** Valid values for msg.what */ 73 public static final int CONNECT_WIRED_HEADSET = 1; 74 public static final int DISCONNECT_WIRED_HEADSET = 2; 75 public static final int CONNECT_BLUETOOTH = 3; 76 public static final int DISCONNECT_BLUETOOTH = 4; 77 public static final int CONNECT_DOCK = 5; 78 public static final int DISCONNECT_DOCK = 6; 79 80 public static final int SWITCH_EARPIECE = 1001; 81 public static final int SWITCH_BLUETOOTH = 1002; 82 public static final int SWITCH_HEADSET = 1003; 83 public static final int SWITCH_SPEAKER = 1004; 84 public static final int SWITCH_WIRED_OR_EARPIECE = 1005; 85 86 public static final int REINITIALIZE = 2001; 87 88 public static final int MUTE_ON = 3001; 89 public static final int MUTE_OFF = 3002; 90 public static final int TOGGLE_MUTE = 3003; 91 92 public static final int SWITCH_FOCUS = 4001; 93 94 // Used in testing to execute verifications. Not compatible with subsessions. 95 public static final int RUN_RUNNABLE = 9001; 96 97 /** Valid values for mAudioFocusType */ 98 public static final int NO_FOCUS = 1; 99 public static final int HAS_FOCUS = 2; 100 101 private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{ 102 put(CONNECT_WIRED_HEADSET, "CONNECT_WIRED_HEADSET"); 103 put(DISCONNECT_WIRED_HEADSET, "DISCONNECT_WIRED_HEADSET"); 104 put(CONNECT_BLUETOOTH, "CONNECT_BLUETOOTH"); 105 put(DISCONNECT_BLUETOOTH, "DISCONNECT_BLUETOOTH"); 106 put(CONNECT_DOCK, "CONNECT_DOCK"); 107 put(DISCONNECT_DOCK, "DISCONNECT_DOCK"); 108 109 put(SWITCH_EARPIECE, "SWITCH_EARPIECE"); 110 put(SWITCH_BLUETOOTH, "SWITCH_BLUETOOTH"); 111 put(SWITCH_HEADSET, "SWITCH_HEADSET"); 112 put(SWITCH_SPEAKER, "SWITCH_SPEAKER"); 113 put(SWITCH_WIRED_OR_EARPIECE, "SWITCH_WIRED_OR_EARPIECE"); 114 115 put(REINITIALIZE, "REINITIALIZE"); 116 117 put(MUTE_ON, "MUTE_ON"); 118 put(MUTE_OFF, "MUTE_OFF"); 119 put(TOGGLE_MUTE, "TOGGLE_MUTE"); 120 121 put(SWITCH_FOCUS, "SWITCH_FOCUS"); 122 123 put(RUN_RUNNABLE, "RUN_RUNNABLE"); 124 }}; 125 126 private static final String ACTIVE_EARPIECE_ROUTE_NAME = "ActiveEarpieceRoute"; 127 private static final String ACTIVE_BLUETOOTH_ROUTE_NAME = "ActiveBluetoothRoute"; 128 private static final String ACTIVE_SPEAKER_ROUTE_NAME = "ActiveSpeakerRoute"; 129 private static final String ACTIVE_HEADSET_ROUTE_NAME = "ActiveHeadsetRoute"; 130 private static final String QUIESCENT_EARPIECE_ROUTE_NAME = "QuiescentEarpieceRoute"; 131 private static final String QUIESCENT_BLUETOOTH_ROUTE_NAME = "QuiescentBluetoothRoute"; 132 private static final String QUIESCENT_SPEAKER_ROUTE_NAME = "QuiescentSpeakerRoute"; 133 private static final String QUIESCENT_HEADSET_ROUTE_NAME = "QuiescentHeadsetRoute"; 134 135 public static final String NAME = CallAudioRouteStateMachine.class.getName(); 136 137 @Override 138 protected void onPreHandleMessage(Message msg) { 139 if (msg.obj != null && msg.obj instanceof Session) { 140 String messageCodeName = MESSAGE_CODE_TO_NAME.get(msg.what, "unknown"); 141 Log.continueSession((Session) msg.obj, "CARSM.pM_" + messageCodeName); 142 Log.i(this, "Message received: %s=%d", messageCodeName, msg.what); 143 } 144 } 145 146 @Override 147 protected void onPostHandleMessage(Message msg) { 148 Log.endSession(); 149 } 150 151 abstract class AudioState extends State { 152 @Override 153 public void enter() { 154 super.enter(); 155 Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE, 156 "Entering state " + getName()); 157 } 158 159 @Override 160 public void exit() { 161 Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE, 162 "Leaving state " + getName()); 163 super.exit(); 164 } 165 166 @Override 167 public boolean processMessage(Message msg) { 168 switch (msg.what) { 169 case CONNECT_WIRED_HEADSET: 170 Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE, 171 "Wired headset connected"); 172 mAvailableRoutes &= ~ROUTE_EARPIECE; 173 mAvailableRoutes |= ROUTE_WIRED_HEADSET; 174 return NOT_HANDLED; 175 case CONNECT_BLUETOOTH: 176 // This case is here because the bluetooth manager sends out a lot of spurious 177 // state changes, and no layers above this one can tell which are actual changes 178 // in connection/disconnection status. This filters it out. 179 if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) { 180 return HANDLED; // Do nothing if we already have bluetooth as enabled. 181 } else { 182 Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE, 183 "Bluetooth connected"); 184 mAvailableRoutes |= ROUTE_BLUETOOTH; 185 return NOT_HANDLED; 186 } 187 case DISCONNECT_WIRED_HEADSET: 188 Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE, 189 "Wired headset disconnected"); 190 mAvailableRoutes &= ~ROUTE_WIRED_HEADSET; 191 mAvailableRoutes |= ROUTE_EARPIECE; 192 return NOT_HANDLED; 193 case DISCONNECT_BLUETOOTH: 194 if ((mAvailableRoutes & ROUTE_BLUETOOTH) == 0) { 195 return HANDLED; 196 } else { 197 Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE, 198 "Bluetooth disconnected"); 199 mAvailableRoutes &= ~ROUTE_BLUETOOTH; 200 return NOT_HANDLED; 201 } 202 case SWITCH_WIRED_OR_EARPIECE: 203 if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) { 204 sendInternalMessage(SWITCH_EARPIECE); 205 } else if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) { 206 sendInternalMessage(SWITCH_HEADSET); 207 } else { 208 Log.e(this, new IllegalStateException(), 209 "Neither headset nor earpiece are available. Defaulting to " + 210 "earpiece."); 211 sendInternalMessage(SWITCH_EARPIECE); 212 } 213 return HANDLED; 214 case REINITIALIZE: 215 CallAudioState initState = getInitialAudioState(); 216 mAvailableRoutes = initState.getSupportedRouteMask(); 217 mIsMuted = initState.isMuted(); 218 mWasOnSpeaker = initState.getRoute() == ROUTE_SPEAKER; 219 transitionTo(mRouteCodeToQuiescentState.get(initState.getRoute())); 220 return HANDLED; 221 default: 222 return NOT_HANDLED; 223 } 224 } 225 226 // Behavior will depend on whether the state is an active one or a quiescent one. 227 abstract public void updateSystemAudioState(); 228 abstract public boolean isActive(); 229 } 230 231 class ActiveEarpieceRoute extends EarpieceRoute { 232 @Override 233 public String getName() { 234 return ACTIVE_EARPIECE_ROUTE_NAME; 235 } 236 237 @Override 238 public boolean isActive() { 239 return true; 240 } 241 242 @Override 243 public void enter() { 244 super.enter(); 245 setSpeakerphoneOn(false); 246 setBluetoothOn(false); 247 CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_EARPIECE, 248 mAvailableRoutes); 249 setSystemAudioState(mCurrentCallAudioState, newState); 250 updateInternalCallAudioState(); 251 } 252 253 @Override 254 public void updateSystemAudioState() { 255 CallAudioState oldAudioState = mCurrentCallAudioState; 256 updateInternalCallAudioState(); 257 setSystemAudioState(oldAudioState, mCurrentCallAudioState); 258 } 259 260 @Override 261 public boolean processMessage(Message msg) { 262 if (super.processMessage(msg) == HANDLED) { 263 return HANDLED; 264 } 265 switch (msg.what) { 266 case SWITCH_EARPIECE: 267 // Nothing to do here 268 return HANDLED; 269 case SWITCH_BLUETOOTH: 270 if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) { 271 transitionTo(mActiveBluetoothRoute); 272 } else { 273 Log.w(this, "Ignoring switch to bluetooth command. Not available."); 274 } 275 return HANDLED; 276 case SWITCH_HEADSET: 277 if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) { 278 transitionTo(mActiveHeadsetRoute); 279 } else { 280 Log.w(this, "Ignoring switch to headset command. Not available."); 281 } 282 return HANDLED; 283 case SWITCH_SPEAKER: 284 transitionTo(mActiveSpeakerRoute); 285 return HANDLED; 286 case SWITCH_FOCUS: 287 if (msg.arg1 == NO_FOCUS) { 288 transitionTo(mQuiescentEarpieceRoute); 289 } 290 return HANDLED; 291 default: 292 return NOT_HANDLED; 293 } 294 } 295 } 296 297 class QuiescentEarpieceRoute extends EarpieceRoute { 298 @Override 299 public String getName() { 300 return QUIESCENT_EARPIECE_ROUTE_NAME; 301 } 302 303 @Override 304 public boolean isActive() { 305 return false; 306 } 307 308 @Override 309 public void enter() { 310 super.enter(); 311 updateInternalCallAudioState(); 312 } 313 314 @Override 315 public void updateSystemAudioState() { 316 updateInternalCallAudioState(); 317 } 318 319 @Override 320 public boolean processMessage(Message msg) { 321 if (super.processMessage(msg) == HANDLED) { 322 return HANDLED; 323 } 324 switch (msg.what) { 325 case SWITCH_EARPIECE: 326 // Nothing to do here 327 return HANDLED; 328 case SWITCH_BLUETOOTH: 329 if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) { 330 transitionTo(mQuiescentBluetoothRoute); 331 } else { 332 Log.w(this, "Ignoring switch to bluetooth command. Not available."); 333 } 334 return HANDLED; 335 case SWITCH_HEADSET: 336 if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) { 337 transitionTo(mQuiescentHeadsetRoute); 338 } else { 339 Log.w(this, "Ignoring switch to headset command. Not available."); 340 } 341 return HANDLED; 342 case SWITCH_SPEAKER: 343 transitionTo(mQuiescentSpeakerRoute); 344 return HANDLED; 345 case SWITCH_FOCUS: 346 if (msg.arg1 == HAS_FOCUS) { 347 transitionTo(mActiveEarpieceRoute); 348 } 349 return HANDLED; 350 default: 351 return NOT_HANDLED; 352 } 353 } 354 } 355 356 abstract class EarpieceRoute extends AudioState { 357 @Override 358 public boolean processMessage(Message msg) { 359 if (super.processMessage(msg) == HANDLED) { 360 return HANDLED; 361 } 362 switch (msg.what) { 363 case CONNECT_WIRED_HEADSET: 364 sendInternalMessage(SWITCH_HEADSET); 365 return HANDLED; 366 case CONNECT_BLUETOOTH: 367 sendInternalMessage(SWITCH_BLUETOOTH); 368 return HANDLED; 369 case DISCONNECT_BLUETOOTH: 370 updateSystemAudioState(); 371 // No change in audio route required 372 return HANDLED; 373 case DISCONNECT_WIRED_HEADSET: 374 Log.e(this, new IllegalStateException(), 375 "Wired headset should not go from connected to not when on " + 376 "earpiece"); 377 updateSystemAudioState(); 378 return HANDLED; 379 case CONNECT_DOCK: 380 sendInternalMessage(SWITCH_SPEAKER); 381 return HANDLED; 382 case DISCONNECT_DOCK: 383 // Nothing to do here 384 return HANDLED; 385 default: 386 return NOT_HANDLED; 387 } 388 } 389 } 390 391 class ActiveHeadsetRoute extends HeadsetRoute { 392 @Override 393 public String getName() { 394 return ACTIVE_HEADSET_ROUTE_NAME; 395 } 396 397 @Override 398 public boolean isActive() { 399 return true; 400 } 401 402 @Override 403 public void enter() { 404 super.enter(); 405 setSpeakerphoneOn(false); 406 setBluetoothOn(false); 407 CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_WIRED_HEADSET, 408 mAvailableRoutes); 409 setSystemAudioState(mCurrentCallAudioState, newState); 410 updateInternalCallAudioState(); 411 } 412 413 @Override 414 public void updateSystemAudioState() { 415 CallAudioState oldAudioState = mCurrentCallAudioState; 416 updateInternalCallAudioState(); 417 setSystemAudioState(oldAudioState, mCurrentCallAudioState); 418 } 419 420 @Override 421 public boolean processMessage(Message msg) { 422 if (super.processMessage(msg) == HANDLED) { 423 return HANDLED; 424 } 425 switch (msg.what) { 426 case SWITCH_EARPIECE: 427 if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) { 428 transitionTo(mActiveEarpieceRoute); 429 } else { 430 Log.w(this, "Ignoring switch to earpiece command. Not available."); 431 } 432 return HANDLED; 433 case SWITCH_BLUETOOTH: 434 if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) { 435 transitionTo(mActiveBluetoothRoute); 436 } else { 437 Log.w(this, "Ignoring switch to bluetooth command. Not available."); 438 } 439 return HANDLED; 440 case SWITCH_HEADSET: 441 // Nothing to do 442 return HANDLED; 443 case SWITCH_SPEAKER: 444 transitionTo(mActiveSpeakerRoute); 445 return HANDLED; 446 case SWITCH_FOCUS: 447 if (msg.arg1 == NO_FOCUS) { 448 transitionTo(mQuiescentHeadsetRoute); 449 } 450 return HANDLED; 451 default: 452 return NOT_HANDLED; 453 } 454 } 455 } 456 457 class QuiescentHeadsetRoute extends HeadsetRoute { 458 @Override 459 public String getName() { 460 return QUIESCENT_HEADSET_ROUTE_NAME; 461 } 462 463 @Override 464 public boolean isActive() { 465 return false; 466 } 467 468 @Override 469 public void enter() { 470 super.enter(); 471 updateInternalCallAudioState(); 472 } 473 474 @Override 475 public void updateSystemAudioState() { 476 updateInternalCallAudioState(); 477 } 478 479 @Override 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 if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) { 487 transitionTo(mQuiescentEarpieceRoute); 488 } else { 489 Log.w(this, "Ignoring switch to earpiece command. Not available."); 490 } 491 return HANDLED; 492 case SWITCH_BLUETOOTH: 493 if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) { 494 transitionTo(mQuiescentBluetoothRoute); 495 } else { 496 Log.w(this, "Ignoring switch to bluetooth command. Not available."); 497 } 498 return HANDLED; 499 case SWITCH_HEADSET: 500 // Nothing to do 501 return HANDLED; 502 case SWITCH_SPEAKER: 503 transitionTo(mQuiescentSpeakerRoute); 504 return HANDLED; 505 case SWITCH_FOCUS: 506 if (msg.arg1 == HAS_FOCUS) { 507 transitionTo(mActiveHeadsetRoute); 508 } 509 return HANDLED; 510 default: 511 return NOT_HANDLED; 512 } 513 } 514 } 515 516 abstract class HeadsetRoute extends AudioState { 517 @Override 518 public boolean processMessage(Message msg) { 519 if (super.processMessage(msg) == HANDLED) { 520 return HANDLED; 521 } 522 switch (msg.what) { 523 case CONNECT_WIRED_HEADSET: 524 Log.e(this, new IllegalStateException(), 525 "Wired headset should already be connected."); 526 mAvailableRoutes |= ROUTE_WIRED_HEADSET; 527 updateSystemAudioState(); 528 return HANDLED; 529 case CONNECT_BLUETOOTH: 530 sendInternalMessage(SWITCH_BLUETOOTH); 531 return HANDLED; 532 case DISCONNECT_BLUETOOTH: 533 updateSystemAudioState(); 534 // No change in audio route required 535 return HANDLED; 536 case DISCONNECT_WIRED_HEADSET: 537 if (mWasOnSpeaker) { 538 sendInternalMessage(SWITCH_SPEAKER); 539 } else { 540 sendInternalMessage(SWITCH_EARPIECE); 541 } 542 return HANDLED; 543 case CONNECT_DOCK: 544 // Nothing to do here 545 return HANDLED; 546 case DISCONNECT_DOCK: 547 // Nothing to do here 548 return HANDLED; 549 default: 550 return NOT_HANDLED; 551 } 552 } 553 } 554 555 class ActiveBluetoothRoute extends BluetoothRoute { 556 @Override 557 public String getName() { 558 return ACTIVE_BLUETOOTH_ROUTE_NAME; 559 } 560 561 @Override 562 public boolean isActive() { 563 return true; 564 } 565 566 @Override 567 public void enter() { 568 super.enter(); 569 setSpeakerphoneOn(false); 570 setBluetoothOn(true); 571 CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_BLUETOOTH, 572 mAvailableRoutes); 573 setSystemAudioState(mCurrentCallAudioState, newState); 574 updateInternalCallAudioState(); 575 } 576 577 @Override 578 public void updateSystemAudioState() { 579 CallAudioState oldAudioState = mCurrentCallAudioState; 580 updateInternalCallAudioState(); 581 setSystemAudioState(oldAudioState, mCurrentCallAudioState); 582 } 583 584 @Override 585 public boolean processMessage(Message msg) { 586 if (super.processMessage(msg) == HANDLED) { 587 return HANDLED; 588 } 589 switch (msg.what) { 590 case SWITCH_EARPIECE: 591 if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) { 592 transitionTo(mActiveEarpieceRoute); 593 } else { 594 Log.w(this, "Ignoring switch to earpiece command. Not available."); 595 } 596 return HANDLED; 597 case SWITCH_BLUETOOTH: 598 // Nothing to do 599 return HANDLED; 600 case SWITCH_HEADSET: 601 if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) { 602 transitionTo(mActiveHeadsetRoute); 603 } else { 604 Log.w(this, "Ignoring switch to headset command. Not available."); 605 } 606 return HANDLED; 607 case SWITCH_SPEAKER: 608 transitionTo(mActiveSpeakerRoute); 609 return HANDLED; 610 case SWITCH_FOCUS: 611 if (msg.arg1 == NO_FOCUS) { 612 transitionTo(mQuiescentBluetoothRoute); 613 } 614 return HANDLED; 615 default: 616 return NOT_HANDLED; 617 } 618 } 619 } 620 621 class QuiescentBluetoothRoute extends BluetoothRoute { 622 @Override 623 public String getName() { 624 return QUIESCENT_BLUETOOTH_ROUTE_NAME; 625 } 626 627 @Override 628 public boolean isActive() { 629 return false; 630 } 631 632 @Override 633 public void enter() { 634 super.enter(); 635 updateInternalCallAudioState(); 636 } 637 638 @Override 639 public void updateSystemAudioState() { 640 updateInternalCallAudioState(); 641 } 642 643 @Override 644 public boolean processMessage(Message msg) { 645 if (super.processMessage(msg) == HANDLED) { 646 return HANDLED; 647 } 648 switch (msg.what) { 649 case SWITCH_EARPIECE: 650 if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) { 651 transitionTo(mQuiescentEarpieceRoute); 652 } else { 653 Log.w(this, "Ignoring switch to earpiece command. Not available."); 654 } 655 return HANDLED; 656 case SWITCH_BLUETOOTH: 657 // Nothing to do 658 return HANDLED; 659 case SWITCH_HEADSET: 660 if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) { 661 transitionTo(mQuiescentHeadsetRoute); 662 } else { 663 Log.w(this, "Ignoring switch to headset command. Not available."); 664 } 665 return HANDLED; 666 case SWITCH_SPEAKER: 667 transitionTo(mQuiescentSpeakerRoute); 668 return HANDLED; 669 case SWITCH_FOCUS: 670 if (msg.arg1 == HAS_FOCUS) { 671 transitionTo(mActiveBluetoothRoute); 672 } 673 return HANDLED; 674 default: 675 return NOT_HANDLED; 676 } 677 } 678 } 679 680 abstract class BluetoothRoute extends AudioState { 681 @Override 682 public boolean processMessage(Message msg) { 683 if (super.processMessage(msg) == HANDLED) { 684 return HANDLED; 685 } 686 switch (msg.what) { 687 case CONNECT_WIRED_HEADSET: 688 sendInternalMessage(SWITCH_HEADSET); 689 return HANDLED; 690 case CONNECT_BLUETOOTH: 691 // We can't tell when a change in bluetooth state corresponds to an 692 // actual connection or disconnection, so we'll just ignore it if we're already 693 // in the bluetooth route. 694 return HANDLED; 695 case DISCONNECT_BLUETOOTH: 696 sendInternalMessage(SWITCH_WIRED_OR_EARPIECE); 697 mWasOnSpeaker = false; 698 return HANDLED; 699 case DISCONNECT_WIRED_HEADSET: 700 updateSystemAudioState(); 701 // No change in audio route required 702 return HANDLED; 703 case CONNECT_DOCK: 704 // Nothing to do here 705 return HANDLED; 706 case DISCONNECT_DOCK: 707 // Nothing to do here 708 return HANDLED; 709 default: 710 return NOT_HANDLED; 711 } 712 } 713 } 714 715 class ActiveSpeakerRoute extends SpeakerRoute { 716 @Override 717 public String getName() { 718 return ACTIVE_SPEAKER_ROUTE_NAME; 719 } 720 721 @Override 722 public boolean isActive() { 723 return true; 724 } 725 726 @Override 727 public void enter() { 728 super.enter(); 729 mWasOnSpeaker = true; 730 setSpeakerphoneOn(true); 731 setBluetoothOn(false); 732 CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_SPEAKER, 733 mAvailableRoutes); 734 setSystemAudioState(mCurrentCallAudioState, newState); 735 updateInternalCallAudioState(); 736 } 737 738 @Override 739 public void updateSystemAudioState() { 740 CallAudioState oldAudioState = mCurrentCallAudioState; 741 updateInternalCallAudioState(); 742 setSystemAudioState(oldAudioState, mCurrentCallAudioState); 743 } 744 745 @Override 746 public boolean processMessage(Message msg) { 747 if (super.processMessage(msg) == HANDLED) { 748 return HANDLED; 749 } 750 switch(msg.what) { 751 case SWITCH_EARPIECE: 752 if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) { 753 transitionTo(mActiveEarpieceRoute); 754 } else { 755 Log.w(this, "Ignoring switch to earpiece command. Not available."); 756 } 757 return HANDLED; 758 case SWITCH_BLUETOOTH: 759 if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) { 760 transitionTo(mActiveBluetoothRoute); 761 } else { 762 Log.w(this, "Ignoring switch to bluetooth command. Not available."); 763 } 764 return HANDLED; 765 case SWITCH_HEADSET: 766 if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) { 767 transitionTo(mActiveHeadsetRoute); 768 } else { 769 Log.w(this, "Ignoring switch to headset command. Not available."); 770 } 771 return HANDLED; 772 case SWITCH_SPEAKER: 773 // Nothing to do 774 return HANDLED; 775 case SWITCH_FOCUS: 776 if (msg.arg1 == NO_FOCUS) { 777 transitionTo(mQuiescentSpeakerRoute); 778 } 779 return HANDLED; 780 default: 781 return NOT_HANDLED; 782 } 783 } 784 } 785 786 class QuiescentSpeakerRoute extends SpeakerRoute { 787 @Override 788 public String getName() { 789 return QUIESCENT_SPEAKER_ROUTE_NAME; 790 } 791 792 @Override 793 public boolean isActive() { 794 return false; 795 } 796 797 @Override 798 public void enter() { 799 super.enter(); 800 // Omit setting mWasOnSpeaker to true here, since this does not reflect a call 801 // actually being on speakerphone. 802 updateInternalCallAudioState(); 803 } 804 805 @Override 806 public void updateSystemAudioState() { 807 updateInternalCallAudioState(); 808 } 809 810 @Override 811 public boolean processMessage(Message msg) { 812 if (super.processMessage(msg) == HANDLED) { 813 return HANDLED; 814 } 815 switch(msg.what) { 816 case SWITCH_EARPIECE: 817 if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) { 818 transitionTo(mQuiescentEarpieceRoute); 819 } else { 820 Log.w(this, "Ignoring switch to earpiece command. Not available."); 821 } 822 return HANDLED; 823 case SWITCH_BLUETOOTH: 824 if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) { 825 transitionTo(mQuiescentBluetoothRoute); 826 } else { 827 Log.w(this, "Ignoring switch to bluetooth command. Not available."); 828 } 829 return HANDLED; 830 case SWITCH_HEADSET: 831 if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) { 832 transitionTo(mQuiescentHeadsetRoute); 833 } else { 834 Log.w(this, "Ignoring switch to headset command. Not available."); 835 } 836 return HANDLED; 837 case SWITCH_SPEAKER: 838 // Nothing to do 839 return HANDLED; 840 case SWITCH_FOCUS: 841 if (msg.arg1 == HAS_FOCUS) { 842 transitionTo(mActiveSpeakerRoute); 843 } 844 return HANDLED; 845 default: 846 return NOT_HANDLED; 847 } 848 } 849 } 850 851 abstract class SpeakerRoute extends AudioState { 852 @Override 853 public boolean processMessage(Message msg) { 854 if (super.processMessage(msg) == HANDLED) { 855 return HANDLED; 856 } 857 switch (msg.what) { 858 case CONNECT_WIRED_HEADSET: 859 sendInternalMessage(SWITCH_HEADSET); 860 return HANDLED; 861 case CONNECT_BLUETOOTH: 862 sendInternalMessage(SWITCH_BLUETOOTH); 863 return HANDLED; 864 case DISCONNECT_BLUETOOTH: 865 updateSystemAudioState(); 866 // No change in audio route required 867 return HANDLED; 868 case DISCONNECT_WIRED_HEADSET: 869 updateSystemAudioState(); 870 // No change in audio route required 871 return HANDLED; 872 case CONNECT_DOCK: 873 // Nothing to do here 874 return HANDLED; 875 case DISCONNECT_DOCK: 876 sendInternalMessage(SWITCH_WIRED_OR_EARPIECE); 877 return HANDLED; 878 default: 879 return NOT_HANDLED; 880 } 881 } 882 } 883 884 private final ActiveEarpieceRoute mActiveEarpieceRoute = new ActiveEarpieceRoute(); 885 private final ActiveHeadsetRoute mActiveHeadsetRoute = new ActiveHeadsetRoute(); 886 private final ActiveBluetoothRoute mActiveBluetoothRoute = new ActiveBluetoothRoute(); 887 private final ActiveSpeakerRoute mActiveSpeakerRoute = new ActiveSpeakerRoute(); 888 private final QuiescentEarpieceRoute mQuiescentEarpieceRoute = new QuiescentEarpieceRoute(); 889 private final QuiescentHeadsetRoute mQuiescentHeadsetRoute = new QuiescentHeadsetRoute(); 890 private final QuiescentBluetoothRoute mQuiescentBluetoothRoute = new QuiescentBluetoothRoute(); 891 private final QuiescentSpeakerRoute mQuiescentSpeakerRoute = new QuiescentSpeakerRoute(); 892 893 /** 894 * A few pieces of hidden state. Used to avoid exponential explosion of number of explicit 895 * states 896 */ 897 private int mAvailableRoutes; 898 private boolean mWasOnSpeaker; 899 private boolean mIsMuted; 900 901 private final Context mContext; 902 private final CallsManager mCallsManager; 903 private final AudioManager mAudioManager; 904 private final BluetoothManager mBluetoothManager; 905 private final WiredHeadsetManager mWiredHeadsetManager; 906 private final StatusBarNotifier mStatusBarNotifier; 907 private final CallAudioManager.AudioServiceFactory mAudioServiceFactory; 908 909 private HashMap<String, Integer> mStateNameToRouteCode; 910 private HashMap<Integer, AudioState> mRouteCodeToQuiescentState; 911 912 // CallAudioState is used as an interface to communicate with many other system components. 913 // No internal state transitions should depend on this variable. 914 private CallAudioState mCurrentCallAudioState; 915 916 public CallAudioRouteStateMachine( 917 Context context, 918 CallsManager callsManager, 919 BluetoothManager bluetoothManager, 920 WiredHeadsetManager wiredHeadsetManager, 921 StatusBarNotifier statusBarNotifier, 922 CallAudioManager.AudioServiceFactory audioServiceFactory) { 923 super(NAME); 924 addState(mActiveEarpieceRoute); 925 addState(mActiveHeadsetRoute); 926 addState(mActiveBluetoothRoute); 927 addState(mActiveSpeakerRoute); 928 addState(mQuiescentEarpieceRoute); 929 addState(mQuiescentHeadsetRoute); 930 addState(mQuiescentBluetoothRoute); 931 addState(mQuiescentSpeakerRoute); 932 933 mContext = context; 934 mCallsManager = callsManager; 935 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 936 mBluetoothManager = bluetoothManager; 937 mWiredHeadsetManager = wiredHeadsetManager; 938 mStatusBarNotifier = statusBarNotifier; 939 mAudioServiceFactory = audioServiceFactory; 940 941 mStateNameToRouteCode = new HashMap<>(8); 942 mStateNameToRouteCode.put(mQuiescentEarpieceRoute.getName(), ROUTE_EARPIECE); 943 mStateNameToRouteCode.put(mQuiescentBluetoothRoute.getName(), ROUTE_BLUETOOTH); 944 mStateNameToRouteCode.put(mQuiescentHeadsetRoute.getName(), ROUTE_WIRED_HEADSET); 945 mStateNameToRouteCode.put(mQuiescentSpeakerRoute.getName(), ROUTE_SPEAKER); 946 mStateNameToRouteCode.put(mActiveEarpieceRoute.getName(), ROUTE_EARPIECE); 947 mStateNameToRouteCode.put(mActiveBluetoothRoute.getName(), ROUTE_BLUETOOTH); 948 mStateNameToRouteCode.put(mActiveHeadsetRoute.getName(), ROUTE_WIRED_HEADSET); 949 mStateNameToRouteCode.put(mActiveSpeakerRoute.getName(), ROUTE_SPEAKER); 950 951 mRouteCodeToQuiescentState = new HashMap<>(4); 952 mRouteCodeToQuiescentState.put(ROUTE_EARPIECE, mQuiescentEarpieceRoute); 953 mRouteCodeToQuiescentState.put(ROUTE_BLUETOOTH, mQuiescentBluetoothRoute); 954 mRouteCodeToQuiescentState.put(ROUTE_SPEAKER, mQuiescentSpeakerRoute); 955 mRouteCodeToQuiescentState.put(ROUTE_WIRED_HEADSET, mQuiescentHeadsetRoute); 956 } 957 958 /** 959 * Initializes the state machine with info on initial audio route, supported audio routes, 960 * and mute status. 961 */ 962 public void initialize() { 963 CallAudioState initState = getInitialAudioState(); 964 initialize(initState); 965 } 966 967 public void initialize(CallAudioState initState) { 968 mCurrentCallAudioState = initState; 969 mAvailableRoutes = initState.getSupportedRouteMask(); 970 mIsMuted = initState.isMuted(); 971 mWasOnSpeaker = initState.getRoute() == ROUTE_SPEAKER; 972 973 mStatusBarNotifier.notifyMute(initState.isMuted()); 974 mStatusBarNotifier.notifySpeakerphone(initState.getRoute() == CallAudioState.ROUTE_SPEAKER); 975 setInitialState(mRouteCodeToQuiescentState.get(initState.getRoute())); 976 start(); 977 } 978 979 /** 980 * Getter for the current CallAudioState object that the state machine is keeping track of. 981 * Used for compatibility purposes. 982 */ 983 public CallAudioState getCurrentCallAudioState() { 984 return mCurrentCallAudioState; 985 } 986 987 public void sendMessageWithSessionInfo(int message, int arg) { 988 sendMessage(message, arg, 0, Log.createSubsession()); 989 } 990 991 public void sendMessageWithSessionInfo(int message) { 992 sendMessage(message, 0, 0, Log.createSubsession()); 993 } 994 995 /** 996 * This is for state-independent changes in audio route (i.e. muting or runnables) 997 * @param msg that couldn't be handled. 998 */ 999 @Override 1000 protected void unhandledMessage(Message msg) { 1001 CallAudioState newCallAudioState; 1002 switch (msg.what) { 1003 case MUTE_ON: 1004 setMuteOn(true); 1005 newCallAudioState = new CallAudioState(mIsMuted, 1006 mCurrentCallAudioState.getRoute(), 1007 mAvailableRoutes); 1008 setSystemAudioState(mCurrentCallAudioState, newCallAudioState); 1009 updateInternalCallAudioState(); 1010 return; 1011 case MUTE_OFF: 1012 setMuteOn(false); 1013 newCallAudioState = new CallAudioState(mIsMuted, 1014 mCurrentCallAudioState.getRoute(), 1015 mAvailableRoutes); 1016 setSystemAudioState(mCurrentCallAudioState, newCallAudioState); 1017 updateInternalCallAudioState(); 1018 return; 1019 case TOGGLE_MUTE: 1020 if (mIsMuted) { 1021 sendInternalMessage(MUTE_OFF); 1022 } else { 1023 sendInternalMessage(MUTE_ON); 1024 } 1025 return; 1026 case RUN_RUNNABLE: 1027 Runnable r = (Runnable) msg.obj; 1028 r.run(); 1029 return; 1030 default: 1031 Log.e(this, new IllegalStateException(), 1032 "Unexpected message code"); 1033 } 1034 } 1035 1036 public void quitStateMachine() { 1037 quitNow(); 1038 } 1039 1040 private void setSpeakerphoneOn(boolean on) { 1041 if (mAudioManager.isSpeakerphoneOn() != on) { 1042 Log.i(this, "turning speaker phone %s", on); 1043 mAudioManager.setSpeakerphoneOn(on); 1044 } 1045 } 1046 1047 private void setBluetoothOn(boolean on) { 1048 if (mBluetoothManager.isBluetoothAvailable()) { 1049 boolean isAlreadyOn = mBluetoothManager.isBluetoothAudioConnectedOrPending(); 1050 if (on != isAlreadyOn) { 1051 Log.i(this, "connecting bluetooth %s", on); 1052 if (on) { 1053 mBluetoothManager.connectBluetoothAudio(); 1054 } else { 1055 mBluetoothManager.disconnectBluetoothAudio(); 1056 } 1057 } 1058 } 1059 } 1060 1061 private void setMuteOn(boolean mute) { 1062 mIsMuted = mute; 1063 Log.event(mCallsManager.getForegroundCall(), Log.Events.MUTE, 1064 mute ? "on" : "off"); 1065 if (mute != mAudioManager.isMicrophoneMute() && isInActiveState()) { 1066 IAudioService audio = mAudioServiceFactory.getAudioService(); 1067 Log.i(this, "changing microphone mute state to: %b [serviceIsNull=%b]", 1068 mute, audio == null); 1069 if (audio != null) { 1070 try { 1071 // We use the audio service directly here so that we can specify 1072 // the current user. Telecom runs in the system_server process which 1073 // may run as a separate user from the foreground user. If we 1074 // used AudioManager directly, we would change mute for the system's 1075 // user and not the current foreground, which we want to avoid. 1076 audio.setMicrophoneMute( 1077 mute, mContext.getOpPackageName(), getCurrentUserId()); 1078 1079 } catch (RemoteException e) { 1080 Log.e(this, e, "Remote exception while toggling mute."); 1081 } 1082 // TODO: Check microphone state after attempting to set to ensure that 1083 // our state corroborates AudioManager's state. 1084 } 1085 } 1086 } 1087 1088 /** 1089 * Updates the CallAudioState object from current internal state. The result is used for 1090 * external communication only. 1091 */ 1092 private void updateInternalCallAudioState() { 1093 IState currentState = getCurrentState(); 1094 if (currentState == null) { 1095 Log.e(this, new IllegalStateException(), "Current state should never be null" + 1096 " when updateInternalCallAudioState is called."); 1097 mCurrentCallAudioState = new CallAudioState( 1098 mIsMuted, mCurrentCallAudioState.getRoute(), mAvailableRoutes); 1099 return; 1100 } 1101 int currentRoute = mStateNameToRouteCode.get(currentState.getName()); 1102 mCurrentCallAudioState = new CallAudioState(mIsMuted, currentRoute, mAvailableRoutes); 1103 } 1104 1105 private void setSystemAudioState(CallAudioState oldCallAudioState, 1106 CallAudioState newCallAudioState) { 1107 Log.i(this, "setSystemAudioState: changing from %s to %s", oldCallAudioState, 1108 newCallAudioState); 1109 Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE, 1110 CallAudioState.audioRouteToString(newCallAudioState.getRoute())); 1111 1112 if (!oldCallAudioState.equals(newCallAudioState)) { 1113 mCallsManager.onCallAudioStateChanged(oldCallAudioState, newCallAudioState); 1114 updateAudioForForegroundCall(newCallAudioState); 1115 } 1116 } 1117 1118 private void updateAudioForForegroundCall(CallAudioState newCallAudioState) { 1119 Call call = mCallsManager.getForegroundCall(); 1120 if (call != null && call.getConnectionService() != null) { 1121 call.getConnectionService().onCallAudioStateChanged(call, newCallAudioState); 1122 } 1123 } 1124 1125 private int calculateSupportedRoutes() { 1126 int routeMask = CallAudioState.ROUTE_SPEAKER; 1127 1128 if (mWiredHeadsetManager.isPluggedIn()) { 1129 routeMask |= CallAudioState.ROUTE_WIRED_HEADSET; 1130 } else { 1131 routeMask |= CallAudioState.ROUTE_EARPIECE; 1132 } 1133 1134 if (mBluetoothManager.isBluetoothAvailable()) { 1135 routeMask |= CallAudioState.ROUTE_BLUETOOTH; 1136 } 1137 1138 return routeMask; 1139 } 1140 1141 private void sendInternalMessage(int messageCode) { 1142 // Internal messages are messages which the state machine sends to itself in the 1143 // course of processing externally-sourced messages. We want to send these messages at 1144 // the front of the queue in order to make actions appear atomic to the user and to 1145 // prevent scenarios such as these: 1146 // 1. State machine handler thread is suspended for some reason. 1147 // 2. Headset gets connected (sends CONNECT_HEADSET). 1148 // 3. User switches to speakerphone in the UI (sends SWITCH_SPEAKER). 1149 // 4. State machine handler is un-suspended. 1150 // 5. State machine handler processes the CONNECT_HEADSET message and sends 1151 // SWITCH_HEADSET at end of queue. 1152 // 6. State machine handler processes SWITCH_SPEAKER. 1153 // 7. State machine handler processes SWITCH_HEADSET. 1154 Session subsession = Log.createSubsession(); 1155 if(subsession != null) { 1156 sendMessageAtFrontOfQueue(messageCode, subsession); 1157 } else { 1158 sendMessageAtFrontOfQueue(messageCode); 1159 } 1160 } 1161 1162 private CallAudioState getInitialAudioState() { 1163 int supportedRouteMask = calculateSupportedRoutes(); 1164 int route = (supportedRouteMask & ROUTE_WIRED_HEADSET) != 0 1165 ? ROUTE_WIRED_HEADSET : ROUTE_EARPIECE; 1166 if ((supportedRouteMask & ROUTE_BLUETOOTH) != 0) { 1167 route = ROUTE_BLUETOOTH; 1168 } 1169 1170 return new CallAudioState(false, route, supportedRouteMask); 1171 } 1172 1173 private int getCurrentUserId() { 1174 final long ident = Binder.clearCallingIdentity(); 1175 try { 1176 UserInfo currentUser = ActivityManagerNative.getDefault().getCurrentUser(); 1177 return currentUser.id; 1178 } catch (RemoteException e) { 1179 // Activity manager not running, nothing we can do assume user 0. 1180 } finally { 1181 Binder.restoreCallingIdentity(ident); 1182 } 1183 return UserHandle.USER_OWNER; 1184 } 1185 1186 private boolean isInActiveState() { 1187 AudioState currentState = (AudioState) getCurrentState(); 1188 if (currentState == null) { 1189 Log.w(this, "Current state is null, assuming inactive state"); 1190 return false; 1191 } 1192 return currentState.isActive(); 1193 } 1194}