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 { 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 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 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 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 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 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 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 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 */ 207 private boolean shouldIgnoreCallForAudio(Call call) { 208 return call.getParentCall() != null || call.isExternalCall(); 209 } 210 211 @Override 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 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 276 public void onHoldToneRequested(Call call) { 277 maybePlayHoldTone(); 278 } 279 280 @Override 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 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 300 public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String message) { 301 maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call); 302 } 303 304 @Override 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 326 public void onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs, 327 ConnectionServiceWrapper newCs) { 328 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 329 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE); 330 } 331 332 @Override 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 351 public CallAudioState getCallAudioState() { 352 return mCallAudioRouteStateMachine.getCurrentCallAudioState(); 353 } 354 355 public Call getPossiblyHeldForegroundCall() { 356 return mForegroundCall; 357 } 358 359 public Call getForegroundCall() { 360 if (mForegroundCall != null && mForegroundCall.getState() != CallState.ON_HOLD) { 361 return mForegroundCall; 362 } 363 return null; 364 } 365 366 void toggleMute() { 367 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 368 CallAudioRouteStateMachine.TOGGLE_MUTE); 369 } 370 371 @VisibleForTesting 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 */ 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 419 void silenceRingers() { 420 for (Call call : mRingingCalls) { 421 call.silence(); 422 } 423 424 mRinger.stopRinging(); 425 mRinger.stopCallWaiting(); 426 } 427 428 @VisibleForTesting 429 public boolean startRinging() { 430 return mRinger.startRinging(mForegroundCall, 431 mCallAudioRouteStateMachine.isHfpDeviceAvailable()); 432 } 433 434 @VisibleForTesting 435 public void startCallWaiting() { 436 if (mRingingCalls.size() == 1) { 437 mRinger.startCallWaiting(mRingingCalls.iterator().next()); 438 } 439 } 440 441 @VisibleForTesting 442 public void stopRinging() { 443 mRinger.stopRinging(); 444 } 445 446 @VisibleForTesting 447 public void stopCallWaiting() { 448 mRinger.stopCallWaiting(); 449 } 450 451 @VisibleForTesting 452 public void setCallAudioRouteFocusState(int focusState) { 453 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 454 CallAudioRouteStateMachine.SWITCH_FOCUS, focusState); 455 } 456 457 @VisibleForTesting 458 public CallAudioRouteStateMachine getCallAudioRouteStateMachine() { 459 return mCallAudioRouteStateMachine; 460 } 461 462 @VisibleForTesting 463 public CallAudioModeStateMachine getCallAudioModeStateMachine() { 464 return mCallAudioModeStateMachine; 465 } 466 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 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 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 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 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 563 private void onCallLeavingRinging() { 564 if (mRingingCalls.size() == 0) { 565 mCallAudioModeStateMachine.sendMessageWithArgs( 566 CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, 567 makeArgsForModeStateMachine()); 568 } 569 } 570 571 private void onCallLeavingHold() { 572 if (mHoldingCalls.size() == 0) { 573 mCallAudioModeStateMachine.sendMessageWithArgs( 574 CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS, 575 makeArgsForModeStateMachine()); 576 } 577 } 578 579 private void onCallEnteringActiveDialingOrConnecting() { 580 if (mActiveDialingOrConnectingCalls.size() == 1) { 581 mCallAudioModeStateMachine.sendMessageWithArgs( 582 CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL, 583 makeArgsForModeStateMachine()); 584 } 585 } 586 587 private void onCallEnteringRinging() { 588 if (mRingingCalls.size() == 1) { 589 mCallAudioModeStateMachine.sendMessageWithArgs( 590 CallAudioModeStateMachine.NEW_RINGING_CALL, 591 makeArgsForModeStateMachine()); 592 } 593 } 594 595 private void onCallEnteringHold() { 596 if (mHoldingCalls.size() == 1) { 597 mCallAudioModeStateMachine.sendMessageWithArgs( 598 CallAudioModeStateMachine.NEW_HOLDING_CALL, 599 makeArgsForModeStateMachine()); 600 } 601 } 602 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 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 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 693 private void playRingbackForCall(Call call) { 694 if (call == mForegroundCall && call.isRingbackRequested()) { 695 mRingbackPlayer.startRingbackForCall(call); 696 } 697 } 698 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 */ 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 */ 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 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 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 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 774 public Set<Call> getTrackedCalls() { 775 return mCalls; 776 } 777 778 @VisibleForTesting 779 public SparseArray<LinkedHashSet<Call>> getCallStateToCalls() { 780 return mCallStateToCalls; 781 } 782 }