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