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