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