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