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 (shouldPlayDisconnectTone(oldState, newState)) { 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 // Turn off mute when a new incoming call is answered. 235 mute(false /* shouldMute */); 236 237 maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call); 238 } 239 240 @Override onSessionModifyRequestReceived(Call call, VideoProfile videoProfile)241 public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) { 242 if (videoProfile == null) { 243 return; 244 } 245 246 if (call != mForegroundCall) { 247 // We only play tones for foreground calls. 248 return; 249 } 250 251 int previousVideoState = call.getVideoState(); 252 int newVideoState = videoProfile.getVideoState(); 253 Log.v(this, "onSessionModifyRequestReceived : videoProfile = " + VideoProfile 254 .videoStateToString(newVideoState)); 255 256 boolean isUpgradeRequest = !VideoProfile.isReceptionEnabled(previousVideoState) && 257 VideoProfile.isReceptionEnabled(newVideoState); 258 259 if (isUpgradeRequest) { 260 mPlayerFactory.createPlayer(InCallTonePlayer.TONE_VIDEO_UPGRADE).startTone(); 261 } 262 } 263 264 /** 265 * Play or stop a call hold tone for a call. Triggered via 266 * {@link Connection#sendConnectionEvent(String)} when the 267 * {@link Connection#EVENT_ON_HOLD_TONE_START} event or 268 * {@link Connection#EVENT_ON_HOLD_TONE_STOP} event is passed through to the 269 * 270 * @param call The call which requested the hold tone. 271 */ 272 @Override onHoldToneRequested(Call call)273 public void onHoldToneRequested(Call call) { 274 maybePlayHoldTone(); 275 } 276 277 @Override onIsVoipAudioModeChanged(Call call)278 public void onIsVoipAudioModeChanged(Call call) { 279 if (call != mForegroundCall) { 280 return; 281 } 282 mCallAudioModeStateMachine.sendMessageWithArgs( 283 CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE, 284 makeArgsForModeStateMachine()); 285 } 286 287 @Override onRingbackRequested(Call call, boolean shouldRingback)288 public void onRingbackRequested(Call call, boolean shouldRingback) { 289 if (call == mForegroundCall && shouldRingback) { 290 mRingbackPlayer.startRingbackForCall(call); 291 } else { 292 mRingbackPlayer.stopRingbackForCall(call); 293 } 294 } 295 296 @Override onIncomingCallRejected(Call call, boolean rejectWithMessage, String message)297 public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String message) { 298 maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call); 299 } 300 301 @Override onIsConferencedChanged(Call call)302 public void onIsConferencedChanged(Call call) { 303 // This indicates a conferencing change, which shouldn't impact any audio mode stuff. 304 Call parentCall = call.getParentCall(); 305 if (parentCall == null) { 306 // Indicates that the call should be tracked for audio purposes. Treat it as if it were 307 // just added. 308 Log.i(LOG_TAG, "Call TC@" + call.getId() + " left conference and will" + 309 " now be tracked by CallAudioManager."); 310 onCallAdded(call); 311 } else { 312 // The call joined a conference, so stop tracking it. 313 if (mCallStateToCalls.get(call.getState()) != null) { 314 mCallStateToCalls.get(call.getState()).remove(call); 315 } 316 317 updateForegroundCall(); 318 mCalls.remove(call); 319 } 320 } 321 322 @Override onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs, ConnectionServiceWrapper newCs)323 public void onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs, 324 ConnectionServiceWrapper newCs) { 325 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 326 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE); 327 } 328 329 @Override onVideoStateChanged(Call call, int previousVideoState, int newVideoState)330 public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) { 331 if (call != getForegroundCall()) { 332 Log.d(LOG_TAG, "Ignoring video state change from %s to %s for call %s -- not " + 333 "foreground.", VideoProfile.videoStateToString(previousVideoState), 334 VideoProfile.videoStateToString(newVideoState), call.getId()); 335 return; 336 } 337 338 if (!VideoProfile.isVideo(previousVideoState) && 339 mCallsManager.isSpeakerphoneAutoEnabledForVideoCalls(newVideoState)) { 340 Log.d(LOG_TAG, "Switching to speaker because call %s transitioned video state from %s" + 341 " to %s", call.getId(), VideoProfile.videoStateToString(previousVideoState), 342 VideoProfile.videoStateToString(newVideoState)); 343 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 344 CallAudioRouteStateMachine.SWITCH_SPEAKER); 345 } 346 } 347 getCallAudioState()348 public CallAudioState getCallAudioState() { 349 return mCallAudioRouteStateMachine.getCurrentCallAudioState(); 350 } 351 getPossiblyHeldForegroundCall()352 public Call getPossiblyHeldForegroundCall() { 353 return mForegroundCall; 354 } 355 getForegroundCall()356 public Call getForegroundCall() { 357 if (mForegroundCall != null && mForegroundCall.getState() != CallState.ON_HOLD) { 358 return mForegroundCall; 359 } 360 return null; 361 } 362 toggleMute()363 void toggleMute() { 364 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 365 CallAudioRouteStateMachine.TOGGLE_MUTE); 366 } 367 368 @VisibleForTesting mute(boolean shouldMute)369 public void mute(boolean shouldMute) { 370 Log.v(this, "mute, shouldMute: %b", shouldMute); 371 372 // Don't mute if there are any emergency calls. 373 if (mCallsManager.hasEmergencyCall()) { 374 shouldMute = false; 375 Log.v(this, "ignoring mute for emergency call"); 376 } 377 378 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(shouldMute 379 ? CallAudioRouteStateMachine.MUTE_ON : CallAudioRouteStateMachine.MUTE_OFF); 380 } 381 382 /** 383 * Changed the audio route, for example from earpiece to speaker phone. 384 * 385 * @param route The new audio route to use. See {@link CallAudioState}. 386 */ setAudioRoute(int route)387 void setAudioRoute(int route) { 388 Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route)); 389 switch (route) { 390 case CallAudioState.ROUTE_BLUETOOTH: 391 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 392 CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH); 393 return; 394 case CallAudioState.ROUTE_SPEAKER: 395 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 396 CallAudioRouteStateMachine.USER_SWITCH_SPEAKER); 397 return; 398 case CallAudioState.ROUTE_WIRED_HEADSET: 399 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 400 CallAudioRouteStateMachine.USER_SWITCH_HEADSET); 401 return; 402 case CallAudioState.ROUTE_EARPIECE: 403 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 404 CallAudioRouteStateMachine.USER_SWITCH_EARPIECE); 405 return; 406 case CallAudioState.ROUTE_WIRED_OR_EARPIECE: 407 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 408 CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE); 409 return; 410 default: 411 Log.wtf(this, "Invalid route specified: %d", route); 412 } 413 } 414 silenceRingers()415 void silenceRingers() { 416 for (Call call : mRingingCalls) { 417 call.silence(); 418 } 419 420 mRingingCalls.clear(); 421 mRinger.stopRinging(); 422 mRinger.stopCallWaiting(); 423 mCallAudioModeStateMachine.sendMessageWithArgs( 424 CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, 425 makeArgsForModeStateMachine()); 426 } 427 428 @VisibleForTesting startRinging()429 public boolean startRinging() { 430 return mRinger.startRinging(mForegroundCall); 431 } 432 433 @VisibleForTesting startCallWaiting()434 public void startCallWaiting() { 435 mRinger.startCallWaiting(mRingingCalls.iterator().next()); 436 } 437 438 @VisibleForTesting stopRinging()439 public void stopRinging() { 440 mRinger.stopRinging(); 441 } 442 443 @VisibleForTesting stopCallWaiting()444 public void stopCallWaiting() { 445 mRinger.stopCallWaiting(); 446 } 447 448 @VisibleForTesting setCallAudioRouteFocusState(int focusState)449 public void setCallAudioRouteFocusState(int focusState) { 450 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 451 CallAudioRouteStateMachine.SWITCH_FOCUS, focusState); 452 } 453 454 @VisibleForTesting getCallAudioRouteStateMachine()455 public CallAudioRouteStateMachine getCallAudioRouteStateMachine() { 456 return mCallAudioRouteStateMachine; 457 } 458 459 @VisibleForTesting getCallAudioModeStateMachine()460 public CallAudioModeStateMachine getCallAudioModeStateMachine() { 461 return mCallAudioModeStateMachine; 462 } 463 dump(IndentingPrintWriter pw)464 void dump(IndentingPrintWriter pw) { 465 pw.println("All calls:"); 466 pw.increaseIndent(); 467 dumpCallsInCollection(pw, mCalls); 468 pw.decreaseIndent(); 469 470 pw.println("Active dialing, or connecting calls:"); 471 pw.increaseIndent(); 472 dumpCallsInCollection(pw, mActiveDialingOrConnectingCalls); 473 pw.decreaseIndent(); 474 475 pw.println("Ringing calls:"); 476 pw.increaseIndent(); 477 dumpCallsInCollection(pw, mRingingCalls); 478 pw.decreaseIndent(); 479 480 pw.println("Holding calls:"); 481 pw.increaseIndent(); 482 dumpCallsInCollection(pw, mHoldingCalls); 483 pw.decreaseIndent(); 484 485 pw.println("Foreground call:"); 486 pw.println(mForegroundCall); 487 } 488 489 @VisibleForTesting setIsTonePlaying(boolean isTonePlaying)490 public void setIsTonePlaying(boolean isTonePlaying) { 491 mIsTonePlaying = isTonePlaying; 492 mCallAudioModeStateMachine.sendMessageWithArgs( 493 isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING 494 : CallAudioModeStateMachine.TONE_STOPPED_PLAYING, 495 makeArgsForModeStateMachine()); 496 } 497 onCallLeavingState(Call call, int state)498 private void onCallLeavingState(Call call, int state) { 499 switch (state) { 500 case CallState.ACTIVE: 501 case CallState.CONNECTING: 502 onCallLeavingActiveDialingOrConnecting(); 503 break; 504 case CallState.RINGING: 505 onCallLeavingRinging(); 506 break; 507 case CallState.ON_HOLD: 508 onCallLeavingHold(); 509 break; 510 case CallState.PULLING: 511 onCallLeavingActiveDialingOrConnecting(); 512 break; 513 case CallState.DIALING: 514 stopRingbackForCall(call); 515 onCallLeavingActiveDialingOrConnecting(); 516 break; 517 } 518 } 519 onCallEnteringState(Call call, int state)520 private void onCallEnteringState(Call call, int state) { 521 switch (state) { 522 case CallState.ACTIVE: 523 case CallState.CONNECTING: 524 onCallEnteringActiveDialingOrConnecting(); 525 break; 526 case CallState.RINGING: 527 onCallEnteringRinging(); 528 break; 529 case CallState.ON_HOLD: 530 onCallEnteringHold(); 531 break; 532 case CallState.PULLING: 533 onCallEnteringActiveDialingOrConnecting(); 534 break; 535 case CallState.DIALING: 536 onCallEnteringActiveDialingOrConnecting(); 537 playRingbackForCall(call); 538 break; 539 } 540 } 541 onCallLeavingActiveDialingOrConnecting()542 private void onCallLeavingActiveDialingOrConnecting() { 543 if (mActiveDialingOrConnectingCalls.size() == 0) { 544 mCallAudioModeStateMachine.sendMessageWithArgs( 545 CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS, 546 makeArgsForModeStateMachine()); 547 } 548 } 549 onCallLeavingRinging()550 private void onCallLeavingRinging() { 551 if (mRingingCalls.size() == 0) { 552 mCallAudioModeStateMachine.sendMessageWithArgs( 553 CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, 554 makeArgsForModeStateMachine()); 555 } 556 } 557 onCallLeavingHold()558 private void onCallLeavingHold() { 559 if (mHoldingCalls.size() == 0) { 560 mCallAudioModeStateMachine.sendMessageWithArgs( 561 CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS, 562 makeArgsForModeStateMachine()); 563 } 564 } 565 onCallEnteringActiveDialingOrConnecting()566 private void onCallEnteringActiveDialingOrConnecting() { 567 if (mActiveDialingOrConnectingCalls.size() == 1) { 568 mCallAudioModeStateMachine.sendMessageWithArgs( 569 CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL, 570 makeArgsForModeStateMachine()); 571 } 572 } 573 onCallEnteringRinging()574 private void onCallEnteringRinging() { 575 if (mRingingCalls.size() == 1) { 576 mCallAudioModeStateMachine.sendMessageWithArgs( 577 CallAudioModeStateMachine.NEW_RINGING_CALL, 578 makeArgsForModeStateMachine()); 579 } 580 } 581 onCallEnteringHold()582 private void onCallEnteringHold() { 583 if (mHoldingCalls.size() == 1) { 584 mCallAudioModeStateMachine.sendMessageWithArgs( 585 CallAudioModeStateMachine.NEW_HOLDING_CALL, 586 makeArgsForModeStateMachine()); 587 } 588 } 589 updateForegroundCall()590 private void updateForegroundCall() { 591 Call oldForegroundCall = mForegroundCall; 592 if (mActiveDialingOrConnectingCalls.size() > 0) { 593 // Give preference for connecting calls over active/dialing for foreground-ness. 594 Call possibleConnectingCall = null; 595 for (Call call : mActiveDialingOrConnectingCalls) { 596 if (call.getState() == CallState.CONNECTING) { 597 possibleConnectingCall = call; 598 } 599 } 600 mForegroundCall = possibleConnectingCall == null ? 601 mActiveDialingOrConnectingCalls.iterator().next() : possibleConnectingCall; 602 } else if (mRingingCalls.size() > 0) { 603 mForegroundCall = mRingingCalls.iterator().next(); 604 } else if (mHoldingCalls.size() > 0) { 605 mForegroundCall = mHoldingCalls.iterator().next(); 606 } else { 607 mForegroundCall = null; 608 } 609 610 if (mForegroundCall != oldForegroundCall) { 611 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 612 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE); 613 mDtmfLocalTonePlayer.onForegroundCallChanged(oldForegroundCall, mForegroundCall); 614 maybePlayHoldTone(); 615 } 616 } 617 618 @NonNull makeArgsForModeStateMachine()619 private CallAudioModeStateMachine.MessageArgs makeArgsForModeStateMachine() { 620 return new CallAudioModeStateMachine.MessageArgs( 621 mActiveDialingOrConnectingCalls.size() > 0, 622 mRingingCalls.size() > 0, 623 mHoldingCalls.size() > 0, 624 mIsTonePlaying, 625 mForegroundCall != null && mForegroundCall.getIsVoipAudioMode(), 626 Log.createSubsession()); 627 } 628 playToneForDisconnectedCall(Call call)629 private void playToneForDisconnectedCall(Call call) { 630 if (mForegroundCall != null && call != mForegroundCall && mCalls.size() > 1) { 631 Log.v(LOG_TAG, "Omitting tone because we are not foreground" + 632 " and there is another call."); 633 return; 634 } 635 636 if (call.getDisconnectCause() != null) { 637 int toneToPlay = InCallTonePlayer.TONE_INVALID; 638 639 Log.v(this, "Disconnect cause: %s.", call.getDisconnectCause()); 640 641 switch(call.getDisconnectCause().getTone()) { 642 case ToneGenerator.TONE_SUP_BUSY: 643 toneToPlay = InCallTonePlayer.TONE_BUSY; 644 break; 645 case ToneGenerator.TONE_SUP_CONGESTION: 646 toneToPlay = InCallTonePlayer.TONE_CONGESTION; 647 break; 648 case ToneGenerator.TONE_CDMA_REORDER: 649 toneToPlay = InCallTonePlayer.TONE_REORDER; 650 break; 651 case ToneGenerator.TONE_CDMA_ABBR_INTERCEPT: 652 toneToPlay = InCallTonePlayer.TONE_INTERCEPT; 653 break; 654 case ToneGenerator.TONE_CDMA_CALLDROP_LITE: 655 toneToPlay = InCallTonePlayer.TONE_CDMA_DROP; 656 break; 657 case ToneGenerator.TONE_SUP_ERROR: 658 toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER; 659 break; 660 case ToneGenerator.TONE_PROP_PROMPT: 661 toneToPlay = InCallTonePlayer.TONE_CALL_ENDED; 662 break; 663 } 664 665 Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay); 666 667 if (toneToPlay != InCallTonePlayer.TONE_INVALID) { 668 mPlayerFactory.createPlayer(toneToPlay).startTone(); 669 } 670 } 671 } 672 playRingbackForCall(Call call)673 private void playRingbackForCall(Call call) { 674 if (call == mForegroundCall && call.isRingbackRequested()) { 675 mRingbackPlayer.startRingbackForCall(call); 676 } 677 } 678 stopRingbackForCall(Call call)679 private void stopRingbackForCall(Call call) { 680 mRingbackPlayer.stopRingbackForCall(call); 681 } 682 683 /** 684 * Determines if a hold tone should be played and then starts or stops it accordingly. 685 */ maybePlayHoldTone()686 private void maybePlayHoldTone() { 687 if (shouldPlayHoldTone()) { 688 if (mHoldTonePlayer == null) { 689 mHoldTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING); 690 mHoldTonePlayer.startTone(); 691 } 692 } else { 693 if (mHoldTonePlayer != null) { 694 mHoldTonePlayer.stopTone(); 695 mHoldTonePlayer = null; 696 } 697 } 698 } 699 700 /** 701 * Determines if a hold tone should be played. 702 * A hold tone should be played only if foreground call is equals with call which is 703 * remotely held. 704 * 705 * @return {@code true} if the the hold tone should be played, {@code false} otherwise. 706 */ shouldPlayHoldTone()707 private boolean shouldPlayHoldTone() { 708 Call foregroundCall = getForegroundCall(); 709 // If there is no foreground call, no hold tone should play. 710 if (foregroundCall == null) { 711 return false; 712 } 713 714 // If another call is ringing, no hold tone should play. 715 if (mCallsManager.hasRingingCall()) { 716 return false; 717 } 718 719 // If the foreground call isn't active, no hold tone should play. This might happen, for 720 // example, if the user puts a remotely held call on hold itself. 721 if (!foregroundCall.isActive()) { 722 return false; 723 } 724 725 return foregroundCall.isRemotelyHeld(); 726 } 727 dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls)728 private void dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls) { 729 for (Call call : calls) { 730 if (call != null) pw.println(call.getId()); 731 } 732 } 733 maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(Call call)734 private void maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(Call call) { 735 // Check to see if the call being answered/rejected is the only ringing call, since this 736 // will be called before the connection service acknowledges the state change. 737 if (mRingingCalls.size() == 0 || 738 (mRingingCalls.size() == 1 && call == mRingingCalls.iterator().next())) { 739 mRinger.stopRinging(); 740 mRinger.stopCallWaiting(); 741 } 742 } 743 shouldPlayDisconnectTone(int oldState, int newState)744 private boolean shouldPlayDisconnectTone(int oldState, int newState) { 745 if (newState != CallState.DISCONNECTED) { 746 return false; 747 } 748 return oldState == CallState.ACTIVE || 749 oldState == CallState.DIALING || 750 oldState == CallState.ON_HOLD; 751 } 752 753 @VisibleForTesting getTrackedCalls()754 public Set<Call> getTrackedCalls() { 755 return mCalls; 756 } 757 758 @VisibleForTesting getCallStateToCalls()759 public SparseArray<LinkedHashSet<Call>> getCallStateToCalls() { 760 return mCallStateToCalls; 761 } 762 }