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}