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