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