CallAudioManager.java revision 6bc865cccb0ba34f3fc0ca83da8eb92c7fe22607
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 19import android.annotation.NonNull; 20import android.media.IAudioService; 21import android.media.ToneGenerator; 22import android.telecom.CallAudioState; 23import android.telecom.Log; 24import android.telecom.VideoProfile; 25import android.util.SparseArray; 26 27import com.android.internal.annotations.VisibleForTesting; 28import com.android.internal.util.IndentingPrintWriter; 29 30import java.util.Collection; 31import java.util.HashSet; 32import java.util.Set; 33import java.util.LinkedHashSet; 34 35public class CallAudioManager extends CallsManagerListenerBase { 36 37 public interface AudioServiceFactory { 38 IAudioService getAudioService(); 39 } 40 41 private final String LOG_TAG = CallAudioManager.class.getSimpleName(); 42 43 private final LinkedHashSet<Call> mActiveDialingOrConnectingCalls; 44 private final LinkedHashSet<Call> mRingingCalls; 45 private final LinkedHashSet<Call> mHoldingCalls; 46 private final Set<Call> mCalls; 47 private final SparseArray<LinkedHashSet<Call>> mCallStateToCalls; 48 49 private final CallAudioRouteStateMachine mCallAudioRouteStateMachine; 50 private final CallAudioModeStateMachine mCallAudioModeStateMachine; 51 private final CallsManager mCallsManager; 52 private final InCallTonePlayer.Factory mPlayerFactory; 53 private final Ringer mRinger; 54 private final RingbackPlayer mRingbackPlayer; 55 private final DtmfLocalTonePlayer mDtmfLocalTonePlayer; 56 57 private Call mForegroundCall; 58 private boolean mIsTonePlaying = false; 59 private boolean mIsDisconnectedTonePlaying = false; 60 private InCallTonePlayer mHoldTonePlayer; 61 62 public CallAudioManager(CallAudioRouteStateMachine callAudioRouteStateMachine, 63 CallsManager callsManager, 64 CallAudioModeStateMachine callAudioModeStateMachine, 65 InCallTonePlayer.Factory playerFactory, 66 Ringer ringer, 67 RingbackPlayer ringbackPlayer, 68 DtmfLocalTonePlayer dtmfLocalTonePlayer) { 69 mActiveDialingOrConnectingCalls = new LinkedHashSet<>(); 70 mRingingCalls = new LinkedHashSet<>(); 71 mHoldingCalls = new LinkedHashSet<>(); 72 mCalls = new HashSet<>(); 73 mCallStateToCalls = new SparseArray<LinkedHashSet<Call>>() {{ 74 put(CallState.CONNECTING, mActiveDialingOrConnectingCalls); 75 put(CallState.ACTIVE, mActiveDialingOrConnectingCalls); 76 put(CallState.DIALING, mActiveDialingOrConnectingCalls); 77 put(CallState.PULLING, mActiveDialingOrConnectingCalls); 78 put(CallState.RINGING, mRingingCalls); 79 put(CallState.ON_HOLD, mHoldingCalls); 80 }}; 81 82 mCallAudioRouteStateMachine = callAudioRouteStateMachine; 83 mCallAudioModeStateMachine = callAudioModeStateMachine; 84 mCallsManager = callsManager; 85 mPlayerFactory = playerFactory; 86 mRinger = ringer; 87 mRingbackPlayer = ringbackPlayer; 88 mDtmfLocalTonePlayer = dtmfLocalTonePlayer; 89 90 mPlayerFactory.setCallAudioManager(this); 91 mCallAudioModeStateMachine.setCallAudioManager(this); 92 } 93 94 @Override 95 public void onCallStateChanged(Call call, int oldState, int newState) { 96 if (shouldIgnoreCallForAudio(call)) { 97 // No audio management for calls in a conference, or external calls. 98 return; 99 } 100 Log.d(LOG_TAG, "Call state changed for TC@%s: %s -> %s", call.getId(), 101 CallState.toString(oldState), CallState.toString(newState)); 102 103 for (int i = 0; i < mCallStateToCalls.size(); i++) { 104 mCallStateToCalls.valueAt(i).remove(call); 105 } 106 if (mCallStateToCalls.get(newState) != null) { 107 mCallStateToCalls.get(newState).add(call); 108 } 109 110 updateForegroundCall(); 111 if (shouldPlayDisconnectTone(oldState, newState)) { 112 playToneForDisconnectedCall(call); 113 } 114 115 onCallLeavingState(call, oldState); 116 onCallEnteringState(call, newState); 117 } 118 119 @Override 120 public void onCallAdded(Call call) { 121 if (shouldIgnoreCallForAudio(call)) { 122 return; // Don't do audio handling for calls in a conference, or external calls. 123 } 124 125 addCall(call); 126 } 127 128 @Override 129 public void onCallRemoved(Call call) { 130 if (shouldIgnoreCallForAudio(call)) { 131 return; // Don't do audio handling for calls in a conference, or external calls. 132 } 133 134 removeCall(call); 135 } 136 137 private void addCall(Call call) { 138 if (mCalls.contains(call)) { 139 Log.w(LOG_TAG, "Call TC@%s is being added twice.", call.getId()); 140 return; // No guarantees that the same call won't get added twice. 141 } 142 143 Log.d(LOG_TAG, "Call added with id TC@%s in state %s", call.getId(), 144 CallState.toString(call.getState())); 145 146 if (mCallStateToCalls.get(call.getState()) != null) { 147 mCallStateToCalls.get(call.getState()).add(call); 148 } 149 updateForegroundCall(); 150 mCalls.add(call); 151 152 onCallEnteringState(call, call.getState()); 153 } 154 155 private void removeCall(Call call) { 156 if (!mCalls.contains(call)) { 157 return; // No guarantees that the same call won't get removed twice. 158 } 159 160 Log.d(LOG_TAG, "Call removed with id TC@%s in state %s", call.getId(), 161 CallState.toString(call.getState())); 162 163 for (int i = 0; i < mCallStateToCalls.size(); i++) { 164 mCallStateToCalls.valueAt(i).remove(call); 165 } 166 167 updateForegroundCall(); 168 mCalls.remove(call); 169 170 onCallLeavingState(call, call.getState()); 171 } 172 173 /** 174 * Handles changes to the external state of a call. External calls which become regular calls 175 * should be tracked, and regular calls which become external should no longer be tracked. 176 * 177 * @param call The call. 178 * @param isExternalCall {@code True} if the call is now external, {@code false} if it is now 179 * a regular call. 180 */ 181 @Override 182 public void onExternalCallChanged(Call call, boolean isExternalCall) { 183 if (isExternalCall) { 184 Log.d(LOG_TAG, "Removing call which became external ID %s", call.getId()); 185 removeCall(call); 186 } else if (!isExternalCall) { 187 Log.d(LOG_TAG, "Adding external call which was pulled with ID %s", call.getId()); 188 addCall(call); 189 190 if (mCallsManager.isSpeakerphoneAutoEnabledForVideoCalls(call.getVideoState())) { 191 // When pulling a video call, automatically enable the speakerphone. 192 Log.d(LOG_TAG, "Switching to speaker because external video call %s was pulled." + 193 call.getId()); 194 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 195 CallAudioRouteStateMachine.SWITCH_SPEAKER); 196 } 197 } 198 } 199 200 /** 201 * Determines if {@link CallAudioManager} should do any audio routing operations for a call. 202 * We ignore child calls of a conference and external calls for audio routing purposes. 203 * 204 * @param call The call to check. 205 * @return {@code true} if the call should be ignored for audio routing, {@code false} 206 * otherwise 207 */ 208 private boolean shouldIgnoreCallForAudio(Call call) { 209 return call.getParentCall() != null || call.isExternalCall(); 210 } 211 212 @Override 213 public void onIncomingCallAnswered(Call call) { 214 if (!mCalls.contains(call)) { 215 return; 216 } 217 218 // This is called after the UI answers the call, but before the connection service 219 // sets the call to active. Only thing to handle for mode here is the audio speedup thing. 220 221 if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) { 222 if (mForegroundCall == call) { 223 Log.i(LOG_TAG, "Invoking the MT_AUDIO_SPEEDUP mechanism. Transitioning into " + 224 "an active in-call audio state before connection service has " + 225 "connected the call."); 226 if (mCallStateToCalls.get(call.getState()) != null) { 227 mCallStateToCalls.get(call.getState()).remove(call); 228 } 229 mActiveDialingOrConnectingCalls.add(call); 230 mCallAudioModeStateMachine.sendMessageWithArgs( 231 CallAudioModeStateMachine.MT_AUDIO_SPEEDUP_FOR_RINGING_CALL, 232 makeArgsForModeStateMachine()); 233 } 234 } 235 236 // Turn off mute when a new incoming call is answered iff it's not a handover. 237 if (!call.isHandoverInProgress()) { 238 mute(false /* shouldMute */); 239 } 240 241 maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call); 242 } 243 244 @Override 245 public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) { 246 if (videoProfile == null) { 247 return; 248 } 249 250 if (call != mForegroundCall) { 251 // We only play tones for foreground calls. 252 return; 253 } 254 255 int previousVideoState = call.getVideoState(); 256 int newVideoState = videoProfile.getVideoState(); 257 Log.v(this, "onSessionModifyRequestReceived : videoProfile = " + VideoProfile 258 .videoStateToString(newVideoState)); 259 260 boolean isUpgradeRequest = !VideoProfile.isReceptionEnabled(previousVideoState) && 261 VideoProfile.isReceptionEnabled(newVideoState); 262 263 if (isUpgradeRequest) { 264 mPlayerFactory.createPlayer(InCallTonePlayer.TONE_VIDEO_UPGRADE).startTone(); 265 } 266 } 267 268 /** 269 * Play or stop a call hold tone for a call. Triggered via 270 * {@link Connection#sendConnectionEvent(String)} when the 271 * {@link Connection#EVENT_ON_HOLD_TONE_START} event or 272 * {@link Connection#EVENT_ON_HOLD_TONE_STOP} event is passed through to the 273 * 274 * @param call The call which requested the hold tone. 275 */ 276 @Override 277 public void onHoldToneRequested(Call call) { 278 maybePlayHoldTone(); 279 } 280 281 @Override 282 public void onIsVoipAudioModeChanged(Call call) { 283 if (call != mForegroundCall) { 284 return; 285 } 286 mCallAudioModeStateMachine.sendMessageWithArgs( 287 CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE, 288 makeArgsForModeStateMachine()); 289 } 290 291 @Override 292 public void onRingbackRequested(Call call, boolean shouldRingback) { 293 if (call == mForegroundCall && shouldRingback) { 294 mRingbackPlayer.startRingbackForCall(call); 295 } else { 296 mRingbackPlayer.stopRingbackForCall(call); 297 } 298 } 299 300 @Override 301 public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String message) { 302 maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call); 303 } 304 305 @Override 306 public void onIsConferencedChanged(Call call) { 307 // This indicates a conferencing change, which shouldn't impact any audio mode stuff. 308 Call parentCall = call.getParentCall(); 309 if (parentCall == null) { 310 // Indicates that the call should be tracked for audio purposes. Treat it as if it were 311 // just added. 312 Log.i(LOG_TAG, "Call TC@" + call.getId() + " left conference and will" + 313 " now be tracked by CallAudioManager."); 314 onCallAdded(call); 315 } else { 316 // The call joined a conference, so stop tracking it. 317 if (mCallStateToCalls.get(call.getState()) != null) { 318 mCallStateToCalls.get(call.getState()).remove(call); 319 } 320 321 updateForegroundCall(); 322 mCalls.remove(call); 323 } 324 } 325 326 @Override 327 public void onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs, 328 ConnectionServiceWrapper newCs) { 329 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 330 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE); 331 } 332 333 @Override 334 public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) { 335 if (call != getForegroundCall()) { 336 Log.d(LOG_TAG, "Ignoring video state change from %s to %s for call %s -- not " + 337 "foreground.", VideoProfile.videoStateToString(previousVideoState), 338 VideoProfile.videoStateToString(newVideoState), call.getId()); 339 return; 340 } 341 342 if (!VideoProfile.isVideo(previousVideoState) && 343 mCallsManager.isSpeakerphoneAutoEnabledForVideoCalls(newVideoState)) { 344 Log.d(LOG_TAG, "Switching to speaker because call %s transitioned video state from %s" + 345 " to %s", call.getId(), VideoProfile.videoStateToString(previousVideoState), 346 VideoProfile.videoStateToString(newVideoState)); 347 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 348 CallAudioRouteStateMachine.SWITCH_SPEAKER); 349 } 350 } 351 352 public CallAudioState getCallAudioState() { 353 return mCallAudioRouteStateMachine.getCurrentCallAudioState(); 354 } 355 356 public Call getPossiblyHeldForegroundCall() { 357 return mForegroundCall; 358 } 359 360 public Call getForegroundCall() { 361 if (mForegroundCall != null && mForegroundCall.getState() != CallState.ON_HOLD) { 362 return mForegroundCall; 363 } 364 return null; 365 } 366 367 @VisibleForTesting 368 public void toggleMute() { 369 // Don't mute if there are any emergency calls. 370 if (mCallsManager.hasEmergencyCall()) { 371 Log.v(this, "ignoring toggleMute for emergency call"); 372 return; 373 } 374 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 375 CallAudioRouteStateMachine.TOGGLE_MUTE); 376 } 377 378 @VisibleForTesting 379 public void mute(boolean shouldMute) { 380 Log.v(this, "mute, shouldMute: %b", shouldMute); 381 382 // Don't mute if there are any emergency calls. 383 if (mCallsManager.hasEmergencyCall()) { 384 shouldMute = false; 385 Log.v(this, "ignoring mute for emergency call"); 386 } 387 388 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(shouldMute 389 ? CallAudioRouteStateMachine.MUTE_ON : CallAudioRouteStateMachine.MUTE_OFF); 390 } 391 392 /** 393 * Changed the audio route, for example from earpiece to speaker phone. 394 * 395 * @param route The new audio route to use. See {@link CallAudioState}. 396 * @param bluetoothAddress the address of the desired bluetooth device, if route is 397 * {@link CallAudioState#ROUTE_BLUETOOTH}. 398 */ 399 void setAudioRoute(int route, String bluetoothAddress) { 400 Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route)); 401 switch (route) { 402 case CallAudioState.ROUTE_BLUETOOTH: 403 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 404 CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH, 0, bluetoothAddress); 405 return; 406 case CallAudioState.ROUTE_SPEAKER: 407 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 408 CallAudioRouteStateMachine.USER_SWITCH_SPEAKER); 409 return; 410 case CallAudioState.ROUTE_WIRED_HEADSET: 411 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 412 CallAudioRouteStateMachine.USER_SWITCH_HEADSET); 413 return; 414 case CallAudioState.ROUTE_EARPIECE: 415 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 416 CallAudioRouteStateMachine.USER_SWITCH_EARPIECE); 417 return; 418 case CallAudioState.ROUTE_WIRED_OR_EARPIECE: 419 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 420 CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE, 421 CallAudioRouteStateMachine.NO_INCLUDE_BLUETOOTH_IN_BASELINE); 422 return; 423 default: 424 Log.wtf(this, "Invalid route specified: %d", route); 425 } 426 } 427 428 /** 429 * Switch call audio routing to the baseline route, including bluetooth headsets if there are 430 * any connected. 431 */ 432 void switchBaseline() { 433 Log.i(this, "switchBaseline"); 434 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 435 CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE, 436 CallAudioRouteStateMachine.INCLUDE_BLUETOOTH_IN_BASELINE); 437 } 438 439 void silenceRingers() { 440 for (Call call : mRingingCalls) { 441 call.silence(); 442 } 443 444 mRinger.stopRinging(); 445 mRinger.stopCallWaiting(); 446 } 447 448 @VisibleForTesting 449 public boolean startRinging() { 450 return mRinger.startRinging(mForegroundCall, 451 mCallAudioRouteStateMachine.isHfpDeviceAvailable()); 452 } 453 454 @VisibleForTesting 455 public void startCallWaiting() { 456 if (mRingingCalls.size() == 1) { 457 mRinger.startCallWaiting(mRingingCalls.iterator().next()); 458 } 459 } 460 461 @VisibleForTesting 462 public void stopRinging() { 463 mRinger.stopRinging(); 464 } 465 466 @VisibleForTesting 467 public void stopCallWaiting() { 468 mRinger.stopCallWaiting(); 469 } 470 471 @VisibleForTesting 472 public void setCallAudioRouteFocusState(int focusState) { 473 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 474 CallAudioRouteStateMachine.SWITCH_FOCUS, focusState); 475 } 476 477 @VisibleForTesting 478 public CallAudioRouteStateMachine getCallAudioRouteStateMachine() { 479 return mCallAudioRouteStateMachine; 480 } 481 482 @VisibleForTesting 483 public CallAudioModeStateMachine getCallAudioModeStateMachine() { 484 return mCallAudioModeStateMachine; 485 } 486 487 void dump(IndentingPrintWriter pw) { 488 pw.println("All calls:"); 489 pw.increaseIndent(); 490 dumpCallsInCollection(pw, mCalls); 491 pw.decreaseIndent(); 492 493 pw.println("Active dialing, or connecting calls:"); 494 pw.increaseIndent(); 495 dumpCallsInCollection(pw, mActiveDialingOrConnectingCalls); 496 pw.decreaseIndent(); 497 498 pw.println("Ringing calls:"); 499 pw.increaseIndent(); 500 dumpCallsInCollection(pw, mRingingCalls); 501 pw.decreaseIndent(); 502 503 pw.println("Holding calls:"); 504 pw.increaseIndent(); 505 dumpCallsInCollection(pw, mHoldingCalls); 506 pw.decreaseIndent(); 507 508 pw.println("Foreground call:"); 509 pw.println(mForegroundCall); 510 511 pw.println("CallAudioModeStateMachine pending messages:"); 512 pw.increaseIndent(); 513 mCallAudioModeStateMachine.dumpPendingMessages(pw); 514 pw.decreaseIndent(); 515 516 pw.println("CallAudioRouteStateMachine pending messages:"); 517 pw.increaseIndent(); 518 mCallAudioRouteStateMachine.dumpPendingMessages(pw); 519 pw.decreaseIndent(); 520 } 521 522 @VisibleForTesting 523 public void setIsTonePlaying(boolean isTonePlaying) { 524 mIsTonePlaying = isTonePlaying; 525 mCallAudioModeStateMachine.sendMessageWithArgs( 526 isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING 527 : CallAudioModeStateMachine.TONE_STOPPED_PLAYING, 528 makeArgsForModeStateMachine()); 529 530 if (!isTonePlaying && mIsDisconnectedTonePlaying) { 531 mCallsManager.onDisconnectedTonePlaying(false); 532 mIsDisconnectedTonePlaying = false; 533 } 534 } 535 536 private void onCallLeavingState(Call call, int state) { 537 switch (state) { 538 case CallState.ACTIVE: 539 case CallState.CONNECTING: 540 onCallLeavingActiveDialingOrConnecting(); 541 break; 542 case CallState.RINGING: 543 onCallLeavingRinging(); 544 break; 545 case CallState.ON_HOLD: 546 onCallLeavingHold(); 547 break; 548 case CallState.PULLING: 549 onCallLeavingActiveDialingOrConnecting(); 550 break; 551 case CallState.DIALING: 552 stopRingbackForCall(call); 553 onCallLeavingActiveDialingOrConnecting(); 554 break; 555 } 556 } 557 558 private void onCallEnteringState(Call call, int state) { 559 switch (state) { 560 case CallState.ACTIVE: 561 case CallState.CONNECTING: 562 onCallEnteringActiveDialingOrConnecting(); 563 break; 564 case CallState.RINGING: 565 onCallEnteringRinging(); 566 break; 567 case CallState.ON_HOLD: 568 onCallEnteringHold(); 569 break; 570 case CallState.PULLING: 571 onCallEnteringActiveDialingOrConnecting(); 572 break; 573 case CallState.DIALING: 574 onCallEnteringActiveDialingOrConnecting(); 575 playRingbackForCall(call); 576 break; 577 } 578 } 579 580 private void onCallLeavingActiveDialingOrConnecting() { 581 if (mActiveDialingOrConnectingCalls.size() == 0) { 582 mCallAudioModeStateMachine.sendMessageWithArgs( 583 CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS, 584 makeArgsForModeStateMachine()); 585 } 586 } 587 588 private void onCallLeavingRinging() { 589 if (mRingingCalls.size() == 0) { 590 mCallAudioModeStateMachine.sendMessageWithArgs( 591 CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, 592 makeArgsForModeStateMachine()); 593 } 594 } 595 596 private void onCallLeavingHold() { 597 if (mHoldingCalls.size() == 0) { 598 mCallAudioModeStateMachine.sendMessageWithArgs( 599 CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS, 600 makeArgsForModeStateMachine()); 601 } 602 } 603 604 private void onCallEnteringActiveDialingOrConnecting() { 605 if (mActiveDialingOrConnectingCalls.size() == 1) { 606 mCallAudioModeStateMachine.sendMessageWithArgs( 607 CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL, 608 makeArgsForModeStateMachine()); 609 } 610 } 611 612 private void onCallEnteringRinging() { 613 if (mRingingCalls.size() == 1) { 614 mCallAudioModeStateMachine.sendMessageWithArgs( 615 CallAudioModeStateMachine.NEW_RINGING_CALL, 616 makeArgsForModeStateMachine()); 617 } 618 } 619 620 private void onCallEnteringHold() { 621 if (mHoldingCalls.size() == 1) { 622 mCallAudioModeStateMachine.sendMessageWithArgs( 623 CallAudioModeStateMachine.NEW_HOLDING_CALL, 624 makeArgsForModeStateMachine()); 625 } 626 } 627 628 private void updateForegroundCall() { 629 Call oldForegroundCall = mForegroundCall; 630 if (mActiveDialingOrConnectingCalls.size() > 0) { 631 // Give preference for connecting calls over active/dialing for foreground-ness. 632 Call possibleConnectingCall = null; 633 for (Call call : mActiveDialingOrConnectingCalls) { 634 if (call.getState() == CallState.CONNECTING) { 635 possibleConnectingCall = call; 636 } 637 } 638 mForegroundCall = possibleConnectingCall == null ? 639 mActiveDialingOrConnectingCalls.iterator().next() : possibleConnectingCall; 640 } else if (mRingingCalls.size() > 0) { 641 mForegroundCall = mRingingCalls.iterator().next(); 642 } else if (mHoldingCalls.size() > 0) { 643 mForegroundCall = mHoldingCalls.iterator().next(); 644 } else { 645 mForegroundCall = null; 646 } 647 648 if (mForegroundCall != oldForegroundCall) { 649 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 650 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE); 651 mDtmfLocalTonePlayer.onForegroundCallChanged(oldForegroundCall, mForegroundCall); 652 maybePlayHoldTone(); 653 } 654 } 655 656 @NonNull 657 private CallAudioModeStateMachine.MessageArgs makeArgsForModeStateMachine() { 658 return new CallAudioModeStateMachine.MessageArgs( 659 mActiveDialingOrConnectingCalls.size() > 0, 660 mRingingCalls.size() > 0, 661 mHoldingCalls.size() > 0, 662 mIsTonePlaying, 663 mForegroundCall != null && mForegroundCall.getIsVoipAudioMode(), 664 Log.createSubsession()); 665 } 666 667 private void playToneForDisconnectedCall(Call call) { 668 // If this call is being disconnected as a result of being handed over to another call, 669 // we will not play a disconnect tone. 670 if (call.isHandoverInProgress()) { 671 Log.i(LOG_TAG, "Omitting tone because %s is being handed over.", call); 672 return; 673 } 674 675 if (mForegroundCall != null && call != mForegroundCall && mCalls.size() > 1) { 676 Log.v(LOG_TAG, "Omitting tone because we are not foreground" + 677 " and there is another call."); 678 return; 679 } 680 681 if (call.getDisconnectCause() != null) { 682 int toneToPlay = InCallTonePlayer.TONE_INVALID; 683 684 Log.v(this, "Disconnect cause: %s.", call.getDisconnectCause()); 685 686 switch(call.getDisconnectCause().getTone()) { 687 case ToneGenerator.TONE_SUP_BUSY: 688 toneToPlay = InCallTonePlayer.TONE_BUSY; 689 break; 690 case ToneGenerator.TONE_SUP_CONGESTION: 691 toneToPlay = InCallTonePlayer.TONE_CONGESTION; 692 break; 693 case ToneGenerator.TONE_CDMA_REORDER: 694 toneToPlay = InCallTonePlayer.TONE_REORDER; 695 break; 696 case ToneGenerator.TONE_CDMA_ABBR_INTERCEPT: 697 toneToPlay = InCallTonePlayer.TONE_INTERCEPT; 698 break; 699 case ToneGenerator.TONE_CDMA_CALLDROP_LITE: 700 toneToPlay = InCallTonePlayer.TONE_CDMA_DROP; 701 break; 702 case ToneGenerator.TONE_SUP_ERROR: 703 toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER; 704 break; 705 case ToneGenerator.TONE_PROP_PROMPT: 706 toneToPlay = InCallTonePlayer.TONE_CALL_ENDED; 707 break; 708 } 709 710 Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay); 711 712 if (toneToPlay != InCallTonePlayer.TONE_INVALID) { 713 mPlayerFactory.createPlayer(toneToPlay).startTone(); 714 mCallsManager.onDisconnectedTonePlaying(true); 715 mIsDisconnectedTonePlaying = true; 716 } 717 } 718 } 719 720 private void playRingbackForCall(Call call) { 721 if (call == mForegroundCall && call.isRingbackRequested()) { 722 mRingbackPlayer.startRingbackForCall(call); 723 } 724 } 725 726 private void stopRingbackForCall(Call call) { 727 mRingbackPlayer.stopRingbackForCall(call); 728 } 729 730 /** 731 * Determines if a hold tone should be played and then starts or stops it accordingly. 732 */ 733 private void maybePlayHoldTone() { 734 if (shouldPlayHoldTone()) { 735 if (mHoldTonePlayer == null) { 736 mHoldTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING); 737 mHoldTonePlayer.startTone(); 738 } 739 } else { 740 if (mHoldTonePlayer != null) { 741 mHoldTonePlayer.stopTone(); 742 mHoldTonePlayer = null; 743 } 744 } 745 } 746 747 /** 748 * Determines if a hold tone should be played. 749 * A hold tone should be played only if foreground call is equals with call which is 750 * remotely held. 751 * 752 * @return {@code true} if the the hold tone should be played, {@code false} otherwise. 753 */ 754 private boolean shouldPlayHoldTone() { 755 Call foregroundCall = getForegroundCall(); 756 // If there is no foreground call, no hold tone should play. 757 if (foregroundCall == null) { 758 return false; 759 } 760 761 // If another call is ringing, no hold tone should play. 762 if (mCallsManager.hasRingingCall()) { 763 return false; 764 } 765 766 // If the foreground call isn't active, no hold tone should play. This might happen, for 767 // example, if the user puts a remotely held call on hold itself. 768 if (!foregroundCall.isActive()) { 769 return false; 770 } 771 772 return foregroundCall.isRemotelyHeld(); 773 } 774 775 private void dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls) { 776 for (Call call : calls) { 777 if (call != null) pw.println(call.getId()); 778 } 779 } 780 781 private void maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(Call call) { 782 // Check to see if the call being answered/rejected is the only ringing call, since this 783 // will be called before the connection service acknowledges the state change. 784 if (mRingingCalls.size() == 0 || 785 (mRingingCalls.size() == 1 && call == mRingingCalls.iterator().next())) { 786 mRinger.stopRinging(); 787 mRinger.stopCallWaiting(); 788 } 789 } 790 791 private boolean shouldPlayDisconnectTone(int oldState, int newState) { 792 if (newState != CallState.DISCONNECTED) { 793 return false; 794 } 795 return oldState == CallState.ACTIVE || 796 oldState == CallState.DIALING || 797 oldState == CallState.ON_HOLD; 798 } 799 800 @VisibleForTesting 801 public Set<Call> getTrackedCalls() { 802 return mCalls; 803 } 804 805 @VisibleForTesting 806 public SparseArray<LinkedHashSet<Call>> getCallStateToCalls() { 807 return mCallStateToCalls; 808 } 809} 810