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