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