CallAudioRouteStateMachine.java revision 629ea10deba347f47ff4b79d1afc14c22d700d68
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.ActivityManager; 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.SystemProperties; 29import android.os.UserHandle; 30import android.telecom.CallAudioState; 31import android.telecom.Log; 32import android.telecom.Logging.Session; 33import android.util.SparseArray; 34 35import com.android.internal.annotations.VisibleForTesting; 36import com.android.internal.util.IState; 37import com.android.internal.util.IndentingPrintWriter; 38import com.android.internal.util.State; 39import com.android.internal.util.StateMachine; 40import com.android.server.telecom.bluetooth.BluetoothRouteManager; 41 42import 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 */ 65public 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 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 185 protected void onPostHandleMessage(Message msg) { 186 Log.endSession(); 187 } 188 189 abstract class AudioState extends State { 190 @Override 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 202 public void exit() { 203 Log.addEvent(mCallsManager.getForegroundCall(), LogUtils.Events.AUDIO_ROUTE, 204 "Leaving state " + getName()); 205 super.exit(); 206 } 207 208 @Override 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. 263 abstract public void updateSystemAudioState(); 264 abstract public boolean isActive(); 265 abstract public int getRouteCode(); 266 } 267 268 class ActiveEarpieceRoute extends EarpieceRoute { 269 @Override 270 public String getName() { 271 return ACTIVE_EARPIECE_ROUTE_NAME; 272 } 273 274 @Override 275 public boolean isActive() { 276 return true; 277 } 278 279 @Override 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 291 public void updateSystemAudioState() { 292 updateInternalCallAudioState(); 293 setSystemAudioState(mCurrentCallAudioState); 294 } 295 296 @Override 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 340 public String getName() { 341 return QUIESCENT_EARPIECE_ROUTE_NAME; 342 } 343 344 @Override 345 public boolean isActive() { 346 return false; 347 } 348 349 @Override 350 public void enter() { 351 super.enter(); 352 mHasUserExplicitlyLeftBluetooth = false; 353 updateInternalCallAudioState(); 354 } 355 356 @Override 357 public void updateSystemAudioState() { 358 updateInternalCallAudioState(); 359 } 360 361 @Override 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 404 public int getRouteCode() { 405 return CallAudioState.ROUTE_EARPIECE; 406 } 407 408 @Override 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 453 public String getName() { 454 return ACTIVE_HEADSET_ROUTE_NAME; 455 } 456 457 @Override 458 public boolean isActive() { 459 return true; 460 } 461 462 @Override 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 474 public void updateSystemAudioState() { 475 updateInternalCallAudioState(); 476 setSystemAudioState(mCurrentCallAudioState); 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 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 523 public String getName() { 524 return QUIESCENT_HEADSET_ROUTE_NAME; 525 } 526 527 @Override 528 public boolean isActive() { 529 return false; 530 } 531 532 @Override 533 public void enter() { 534 super.enter(); 535 mHasUserExplicitlyLeftBluetooth = false; 536 updateInternalCallAudioState(); 537 } 538 539 @Override 540 public void updateSystemAudioState() { 541 updateInternalCallAudioState(); 542 } 543 544 @Override 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 587 public int getRouteCode() { 588 return CallAudioState.ROUTE_WIRED_HEADSET; 589 } 590 591 @Override 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 640 public String getName() { 641 return ACTIVE_BLUETOOTH_ROUTE_NAME; 642 } 643 644 @Override 645 public boolean isActive() { 646 return true; 647 } 648 649 @Override 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 661 public void updateSystemAudioState() { 662 updateInternalCallAudioState(); 663 setSystemAudioState(mCurrentCallAudioState); 664 } 665 666 @Override 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 720 public String getName() { 721 return RINGING_BLUETOOTH_ROUTE_NAME; 722 } 723 724 @Override 725 public boolean isActive() { 726 return false; 727 } 728 729 @Override 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 741 public void updateSystemAudioState() { 742 updateInternalCallAudioState(); 743 setSystemAudioState(mCurrentCallAudioState); 744 } 745 746 @Override 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 801 public String getName() { 802 return QUIESCENT_BLUETOOTH_ROUTE_NAME; 803 } 804 805 @Override 806 public boolean isActive() { 807 return false; 808 } 809 810 @Override 811 public void enter() { 812 super.enter(); 813 mHasUserExplicitlyLeftBluetooth = false; 814 updateInternalCallAudioState(); 815 setBluetoothOn(false); 816 } 817 818 @Override 819 public void updateSystemAudioState() { 820 updateInternalCallAudioState(); 821 } 822 823 @Override 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 872 public int getRouteCode() { 873 return CallAudioState.ROUTE_BLUETOOTH; 874 } 875 876 @Override 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 912 public String getName() { 913 return ACTIVE_SPEAKER_ROUTE_NAME; 914 } 915 916 @Override 917 public boolean isActive() { 918 return true; 919 } 920 921 @Override 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 934 public void updateSystemAudioState() { 935 updateInternalCallAudioState(); 936 setSystemAudioState(mCurrentCallAudioState); 937 } 938 939 @Override 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 993 public String getName() { 994 return QUIESCENT_SPEAKER_ROUTE_NAME; 995 } 996 997 @Override 998 public boolean isActive() { 999 return false; 1000 } 1001 1002 @Override 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 1012 public void updateSystemAudioState() { 1013 updateInternalCallAudioState(); 1014 } 1015 1016 @Override 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 1063 public int getRouteCode() { 1064 return CallAudioState.ROUTE_SPEAKER; 1065 } 1066 1067 @Override 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 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 */ 1198 public void initialize() { 1199 CallAudioState initState = getInitialAudioState(); 1200 initialize(initState); 1201 } 1202 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 */ 1226 public CallAudioState getCurrentCallAudioState() { 1227 return mCurrentCallAudioState; 1228 } 1229 1230 public void sendMessageWithSessionInfo(int message, int arg) { 1231 sendMessage(message, arg, 0, Log.createSubsession()); 1232 } 1233 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 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 1283 public void quitStateMachine() { 1284 quitNow(); 1285 } 1286 1287 public void dumpPendingMessages(IndentingPrintWriter pw) { 1288 getHandler().getLooper().dump(pw::println, ""); 1289 } 1290 1291 public boolean isHfpDeviceAvailable() { 1292 return mBluetoothRouteManager.isBluetoothAvailable(); 1293 } 1294 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 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 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 */ 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 1361 private void setSystemAudioState(CallAudioState newCallAudioState) { 1362 setSystemAudioState(newCallAudioState, false); 1363 } 1364 1365 private void resendSystemAudioState() { 1366 setSystemAudioState(mLastKnownCallAudioState, true); 1367 } 1368 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 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 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 1405 private void sendInternalMessage(int messageCode) { 1406 sendInternalMessage(messageCode, 0); 1407 } 1408 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 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 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 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 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 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 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 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 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 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