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 17package com.android.server.telecom; 18 19import android.annotation.NonNull; 20import android.media.IAudioService; 21import android.media.ToneGenerator; 22import android.telecom.CallAudioState; 23import android.telecom.VideoProfile; 24import android.util.SparseArray; 25 26import com.android.internal.annotations.VisibleForTesting; 27import com.android.internal.util.IndentingPrintWriter; 28 29import java.util.Collection; 30import java.util.HashSet; 31import java.util.Set; 32import java.util.LinkedHashSet; 33 34public class CallAudioManager extends CallsManagerListenerBase { 35 36 public interface AudioServiceFactory { 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 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 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 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 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 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 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 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 */ 206 private boolean shouldIgnoreCallForAudio(Call call) { 207 return call.getParentCall() != null || call.isExternalCall(); 208 } 209 210 @Override 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 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 273 public void onHoldToneRequested(Call call) { 274 maybePlayHoldTone(); 275 } 276 277 @Override 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 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 297 public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String message) { 298 maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call); 299 } 300 301 @Override 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 323 public void onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs, 324 ConnectionServiceWrapper newCs) { 325 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 326 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE); 327 } 328 329 @Override 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 348 public CallAudioState getCallAudioState() { 349 return mCallAudioRouteStateMachine.getCurrentCallAudioState(); 350 } 351 352 public Call getPossiblyHeldForegroundCall() { 353 return mForegroundCall; 354 } 355 356 public Call getForegroundCall() { 357 if (mForegroundCall != null && mForegroundCall.getState() != CallState.ON_HOLD) { 358 return mForegroundCall; 359 } 360 return null; 361 } 362 363 void toggleMute() { 364 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 365 CallAudioRouteStateMachine.TOGGLE_MUTE); 366 } 367 368 @VisibleForTesting 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 */ 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 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 429 public boolean startRinging() { 430 return mRinger.startRinging(mForegroundCall); 431 } 432 433 @VisibleForTesting 434 public void startCallWaiting() { 435 mRinger.startCallWaiting(mRingingCalls.iterator().next()); 436 } 437 438 @VisibleForTesting 439 public void stopRinging() { 440 mRinger.stopRinging(); 441 } 442 443 @VisibleForTesting 444 public void stopCallWaiting() { 445 mRinger.stopCallWaiting(); 446 } 447 448 @VisibleForTesting 449 public void setCallAudioRouteFocusState(int focusState) { 450 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 451 CallAudioRouteStateMachine.SWITCH_FOCUS, focusState); 452 } 453 454 @VisibleForTesting 455 public CallAudioRouteStateMachine getCallAudioRouteStateMachine() { 456 return mCallAudioRouteStateMachine; 457 } 458 459 @VisibleForTesting 460 public CallAudioModeStateMachine getCallAudioModeStateMachine() { 461 return mCallAudioModeStateMachine; 462 } 463 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 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 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 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 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 550 private void onCallLeavingRinging() { 551 if (mRingingCalls.size() == 0) { 552 mCallAudioModeStateMachine.sendMessageWithArgs( 553 CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, 554 makeArgsForModeStateMachine()); 555 } 556 } 557 558 private void onCallLeavingHold() { 559 if (mHoldingCalls.size() == 0) { 560 mCallAudioModeStateMachine.sendMessageWithArgs( 561 CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS, 562 makeArgsForModeStateMachine()); 563 } 564 } 565 566 private void onCallEnteringActiveDialingOrConnecting() { 567 if (mActiveDialingOrConnectingCalls.size() == 1) { 568 mCallAudioModeStateMachine.sendMessageWithArgs( 569 CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL, 570 makeArgsForModeStateMachine()); 571 } 572 } 573 574 private void onCallEnteringRinging() { 575 if (mRingingCalls.size() == 1) { 576 mCallAudioModeStateMachine.sendMessageWithArgs( 577 CallAudioModeStateMachine.NEW_RINGING_CALL, 578 makeArgsForModeStateMachine()); 579 } 580 } 581 582 private void onCallEnteringHold() { 583 if (mHoldingCalls.size() == 1) { 584 mCallAudioModeStateMachine.sendMessageWithArgs( 585 CallAudioModeStateMachine.NEW_HOLDING_CALL, 586 makeArgsForModeStateMachine()); 587 } 588 } 589 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 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 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 673 private void playRingbackForCall(Call call) { 674 if (call == mForegroundCall && call.isRingbackRequested()) { 675 mRingbackPlayer.startRingbackForCall(call); 676 } 677 } 678 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 */ 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 */ 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 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 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 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 754 public Set<Call> getTrackedCalls() { 755 return mCalls; 756 } 757 758 @VisibleForTesting 759 public SparseArray<LinkedHashSet<Call>> getCallStateToCalls() { 760 return mCallStateToCalls; 761 } 762}