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