CallAudioManager.java revision 6bc865cccb0ba34f3fc0ca83da8eb92c7fe22607
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 boolean mIsDisconnectedTonePlaying = false;
60    private InCallTonePlayer mHoldTonePlayer;
61
62    public CallAudioManager(CallAudioRouteStateMachine callAudioRouteStateMachine,
63            CallsManager callsManager,
64            CallAudioModeStateMachine callAudioModeStateMachine,
65            InCallTonePlayer.Factory playerFactory,
66            Ringer ringer,
67            RingbackPlayer ringbackPlayer,
68            DtmfLocalTonePlayer dtmfLocalTonePlayer) {
69        mActiveDialingOrConnectingCalls = new LinkedHashSet<>();
70        mRingingCalls = new LinkedHashSet<>();
71        mHoldingCalls = new LinkedHashSet<>();
72        mCalls = new HashSet<>();
73        mCallStateToCalls = new SparseArray<LinkedHashSet<Call>>() {{
74            put(CallState.CONNECTING, mActiveDialingOrConnectingCalls);
75            put(CallState.ACTIVE, mActiveDialingOrConnectingCalls);
76            put(CallState.DIALING, mActiveDialingOrConnectingCalls);
77            put(CallState.PULLING, mActiveDialingOrConnectingCalls);
78            put(CallState.RINGING, mRingingCalls);
79            put(CallState.ON_HOLD, mHoldingCalls);
80        }};
81
82        mCallAudioRouteStateMachine = callAudioRouteStateMachine;
83        mCallAudioModeStateMachine = callAudioModeStateMachine;
84        mCallsManager = callsManager;
85        mPlayerFactory = playerFactory;
86        mRinger = ringer;
87        mRingbackPlayer = ringbackPlayer;
88        mDtmfLocalTonePlayer = dtmfLocalTonePlayer;
89
90        mPlayerFactory.setCallAudioManager(this);
91        mCallAudioModeStateMachine.setCallAudioManager(this);
92    }
93
94    @Override
95    public void onCallStateChanged(Call call, int oldState, int newState) {
96        if (shouldIgnoreCallForAudio(call)) {
97            // No audio management for calls in a conference, or external calls.
98            return;
99        }
100        Log.d(LOG_TAG, "Call state changed for TC@%s: %s -> %s", call.getId(),
101                CallState.toString(oldState), CallState.toString(newState));
102
103        for (int i = 0; i < mCallStateToCalls.size(); i++) {
104            mCallStateToCalls.valueAt(i).remove(call);
105        }
106        if (mCallStateToCalls.get(newState) != null) {
107            mCallStateToCalls.get(newState).add(call);
108        }
109
110        updateForegroundCall();
111        if (shouldPlayDisconnectTone(oldState, newState)) {
112            playToneForDisconnectedCall(call);
113        }
114
115        onCallLeavingState(call, oldState);
116        onCallEnteringState(call, newState);
117    }
118
119    @Override
120    public void onCallAdded(Call call) {
121        if (shouldIgnoreCallForAudio(call)) {
122            return; // Don't do audio handling for calls in a conference, or external calls.
123        }
124
125        addCall(call);
126    }
127
128    @Override
129    public void onCallRemoved(Call call) {
130        if (shouldIgnoreCallForAudio(call)) {
131            return; // Don't do audio handling for calls in a conference, or external calls.
132        }
133
134        removeCall(call);
135    }
136
137    private void addCall(Call call) {
138        if (mCalls.contains(call)) {
139            Log.w(LOG_TAG, "Call TC@%s is being added twice.", call.getId());
140            return; // No guarantees that the same call won't get added twice.
141        }
142
143        Log.d(LOG_TAG, "Call added with id TC@%s in state %s", call.getId(),
144                CallState.toString(call.getState()));
145
146        if (mCallStateToCalls.get(call.getState()) != null) {
147            mCallStateToCalls.get(call.getState()).add(call);
148        }
149        updateForegroundCall();
150        mCalls.add(call);
151
152        onCallEnteringState(call, call.getState());
153    }
154
155    private void removeCall(Call call) {
156        if (!mCalls.contains(call)) {
157            return; // No guarantees that the same call won't get removed twice.
158        }
159
160        Log.d(LOG_TAG, "Call removed with id TC@%s in state %s", call.getId(),
161                CallState.toString(call.getState()));
162
163        for (int i = 0; i < mCallStateToCalls.size(); i++) {
164            mCallStateToCalls.valueAt(i).remove(call);
165        }
166
167        updateForegroundCall();
168        mCalls.remove(call);
169
170        onCallLeavingState(call, call.getState());
171    }
172
173    /**
174     * Handles changes to the external state of a call.  External calls which become regular calls
175     * should be tracked, and regular calls which become external should no longer be tracked.
176     *
177     * @param call The call.
178     * @param isExternalCall {@code True} if the call is now external, {@code false} if it is now
179     *      a regular call.
180     */
181    @Override
182    public void onExternalCallChanged(Call call, boolean isExternalCall) {
183        if (isExternalCall) {
184            Log.d(LOG_TAG, "Removing call which became external ID %s", call.getId());
185            removeCall(call);
186        } else if (!isExternalCall) {
187            Log.d(LOG_TAG, "Adding external call which was pulled with ID %s", call.getId());
188            addCall(call);
189
190            if (mCallsManager.isSpeakerphoneAutoEnabledForVideoCalls(call.getVideoState())) {
191                // When pulling a video call, automatically enable the speakerphone.
192                Log.d(LOG_TAG, "Switching to speaker because external video call %s was pulled." +
193                        call.getId());
194                mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
195                        CallAudioRouteStateMachine.SWITCH_SPEAKER);
196            }
197        }
198    }
199
200    /**
201     * Determines if {@link CallAudioManager} should do any audio routing operations for a call.
202     * We ignore child calls of a conference and external calls for audio routing purposes.
203     *
204     * @param call The call to check.
205     * @return {@code true} if the call should be ignored for audio routing, {@code false}
206     * otherwise
207     */
208    private boolean shouldIgnoreCallForAudio(Call call) {
209        return call.getParentCall() != null || call.isExternalCall();
210    }
211
212    @Override
213    public void onIncomingCallAnswered(Call call) {
214        if (!mCalls.contains(call)) {
215            return;
216        }
217
218        // This is called after the UI answers the call, but before the connection service
219        // sets the call to active. Only thing to handle for mode here is the audio speedup thing.
220
221        if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) {
222            if (mForegroundCall == call) {
223                Log.i(LOG_TAG, "Invoking the MT_AUDIO_SPEEDUP mechanism. Transitioning into " +
224                        "an active in-call audio state before connection service has " +
225                        "connected the call.");
226                if (mCallStateToCalls.get(call.getState()) != null) {
227                    mCallStateToCalls.get(call.getState()).remove(call);
228                }
229                mActiveDialingOrConnectingCalls.add(call);
230                mCallAudioModeStateMachine.sendMessageWithArgs(
231                        CallAudioModeStateMachine.MT_AUDIO_SPEEDUP_FOR_RINGING_CALL,
232                        makeArgsForModeStateMachine());
233            }
234        }
235
236        // Turn off mute when a new incoming call is answered iff it's not a handover.
237        if (!call.isHandoverInProgress()) {
238            mute(false /* shouldMute */);
239        }
240
241        maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call);
242    }
243
244    @Override
245    public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) {
246        if (videoProfile == null) {
247            return;
248        }
249
250        if (call != mForegroundCall) {
251            // We only play tones for foreground calls.
252            return;
253        }
254
255        int previousVideoState = call.getVideoState();
256        int newVideoState = videoProfile.getVideoState();
257        Log.v(this, "onSessionModifyRequestReceived : videoProfile = " + VideoProfile
258                .videoStateToString(newVideoState));
259
260        boolean isUpgradeRequest = !VideoProfile.isReceptionEnabled(previousVideoState) &&
261                VideoProfile.isReceptionEnabled(newVideoState);
262
263        if (isUpgradeRequest) {
264            mPlayerFactory.createPlayer(InCallTonePlayer.TONE_VIDEO_UPGRADE).startTone();
265        }
266    }
267
268    /**
269     * Play or stop a call hold tone for a call.  Triggered via
270     * {@link Connection#sendConnectionEvent(String)} when the
271     * {@link Connection#EVENT_ON_HOLD_TONE_START} event or
272     * {@link Connection#EVENT_ON_HOLD_TONE_STOP} event is passed through to the
273     *
274     * @param call The call which requested the hold tone.
275     */
276    @Override
277    public void onHoldToneRequested(Call call) {
278        maybePlayHoldTone();
279    }
280
281    @Override
282    public void onIsVoipAudioModeChanged(Call call) {
283        if (call != mForegroundCall) {
284            return;
285        }
286        mCallAudioModeStateMachine.sendMessageWithArgs(
287                CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE,
288                makeArgsForModeStateMachine());
289    }
290
291    @Override
292    public void onRingbackRequested(Call call, boolean shouldRingback) {
293        if (call == mForegroundCall && shouldRingback) {
294            mRingbackPlayer.startRingbackForCall(call);
295        } else {
296            mRingbackPlayer.stopRingbackForCall(call);
297        }
298    }
299
300    @Override
301    public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String message) {
302        maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call);
303    }
304
305    @Override
306    public void onIsConferencedChanged(Call call) {
307        // This indicates a conferencing change, which shouldn't impact any audio mode stuff.
308        Call parentCall = call.getParentCall();
309        if (parentCall == null) {
310            // Indicates that the call should be tracked for audio purposes. Treat it as if it were
311            // just added.
312            Log.i(LOG_TAG, "Call TC@" + call.getId() + " left conference and will" +
313                            " now be tracked by CallAudioManager.");
314            onCallAdded(call);
315        } else {
316            // The call joined a conference, so stop tracking it.
317            if (mCallStateToCalls.get(call.getState()) != null) {
318                mCallStateToCalls.get(call.getState()).remove(call);
319            }
320
321            updateForegroundCall();
322            mCalls.remove(call);
323        }
324    }
325
326    @Override
327    public void onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs,
328            ConnectionServiceWrapper newCs) {
329        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
330                CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
331    }
332
333    @Override
334    public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {
335        if (call != getForegroundCall()) {
336            Log.d(LOG_TAG, "Ignoring video state change from %s to %s for call %s -- not " +
337                    "foreground.", VideoProfile.videoStateToString(previousVideoState),
338                    VideoProfile.videoStateToString(newVideoState), call.getId());
339            return;
340        }
341
342        if (!VideoProfile.isVideo(previousVideoState) &&
343                mCallsManager.isSpeakerphoneAutoEnabledForVideoCalls(newVideoState)) {
344            Log.d(LOG_TAG, "Switching to speaker because call %s transitioned video state from %s" +
345                    " to %s", call.getId(), VideoProfile.videoStateToString(previousVideoState),
346                    VideoProfile.videoStateToString(newVideoState));
347            mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
348                    CallAudioRouteStateMachine.SWITCH_SPEAKER);
349        }
350    }
351
352    public CallAudioState getCallAudioState() {
353        return mCallAudioRouteStateMachine.getCurrentCallAudioState();
354    }
355
356    public Call getPossiblyHeldForegroundCall() {
357        return mForegroundCall;
358    }
359
360    public Call getForegroundCall() {
361        if (mForegroundCall != null && mForegroundCall.getState() != CallState.ON_HOLD) {
362            return mForegroundCall;
363        }
364        return null;
365    }
366
367    @VisibleForTesting
368    public void toggleMute() {
369        // Don't mute if there are any emergency calls.
370        if (mCallsManager.hasEmergencyCall()) {
371            Log.v(this, "ignoring toggleMute for emergency call");
372            return;
373        }
374        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
375                CallAudioRouteStateMachine.TOGGLE_MUTE);
376    }
377
378    @VisibleForTesting
379    public void mute(boolean shouldMute) {
380        Log.v(this, "mute, shouldMute: %b", shouldMute);
381
382        // Don't mute if there are any emergency calls.
383        if (mCallsManager.hasEmergencyCall()) {
384            shouldMute = false;
385            Log.v(this, "ignoring mute for emergency call");
386        }
387
388        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(shouldMute
389                ? CallAudioRouteStateMachine.MUTE_ON : CallAudioRouteStateMachine.MUTE_OFF);
390    }
391
392    /**
393     * Changed the audio route, for example from earpiece to speaker phone.
394     *
395     * @param route The new audio route to use. See {@link CallAudioState}.
396     * @param bluetoothAddress the address of the desired bluetooth device, if route is
397     * {@link CallAudioState#ROUTE_BLUETOOTH}.
398     */
399    void setAudioRoute(int route, String bluetoothAddress) {
400        Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route));
401        switch (route) {
402            case CallAudioState.ROUTE_BLUETOOTH:
403                mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
404                        CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH, 0, bluetoothAddress);
405                return;
406            case CallAudioState.ROUTE_SPEAKER:
407                mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
408                        CallAudioRouteStateMachine.USER_SWITCH_SPEAKER);
409                return;
410            case CallAudioState.ROUTE_WIRED_HEADSET:
411                mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
412                        CallAudioRouteStateMachine.USER_SWITCH_HEADSET);
413                return;
414            case CallAudioState.ROUTE_EARPIECE:
415                mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
416                        CallAudioRouteStateMachine.USER_SWITCH_EARPIECE);
417                return;
418            case CallAudioState.ROUTE_WIRED_OR_EARPIECE:
419                mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
420                        CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE,
421                        CallAudioRouteStateMachine.NO_INCLUDE_BLUETOOTH_IN_BASELINE);
422                return;
423            default:
424                Log.wtf(this, "Invalid route specified: %d", route);
425        }
426    }
427
428    /**
429     * Switch call audio routing to the baseline route, including bluetooth headsets if there are
430     * any connected.
431     */
432    void switchBaseline() {
433        Log.i(this, "switchBaseline");
434        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
435                CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE,
436                CallAudioRouteStateMachine.INCLUDE_BLUETOOTH_IN_BASELINE);
437    }
438
439    void silenceRingers() {
440        for (Call call : mRingingCalls) {
441            call.silence();
442        }
443
444        mRinger.stopRinging();
445        mRinger.stopCallWaiting();
446    }
447
448    @VisibleForTesting
449    public boolean startRinging() {
450        return mRinger.startRinging(mForegroundCall,
451                mCallAudioRouteStateMachine.isHfpDeviceAvailable());
452    }
453
454    @VisibleForTesting
455    public void startCallWaiting() {
456        if (mRingingCalls.size() == 1) {
457            mRinger.startCallWaiting(mRingingCalls.iterator().next());
458        }
459    }
460
461    @VisibleForTesting
462    public void stopRinging() {
463        mRinger.stopRinging();
464    }
465
466    @VisibleForTesting
467    public void stopCallWaiting() {
468        mRinger.stopCallWaiting();
469    }
470
471    @VisibleForTesting
472    public void setCallAudioRouteFocusState(int focusState) {
473        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
474                CallAudioRouteStateMachine.SWITCH_FOCUS, focusState);
475    }
476
477    @VisibleForTesting
478    public CallAudioRouteStateMachine getCallAudioRouteStateMachine() {
479        return mCallAudioRouteStateMachine;
480    }
481
482    @VisibleForTesting
483    public CallAudioModeStateMachine getCallAudioModeStateMachine() {
484        return mCallAudioModeStateMachine;
485    }
486
487    void dump(IndentingPrintWriter pw) {
488        pw.println("All calls:");
489        pw.increaseIndent();
490        dumpCallsInCollection(pw, mCalls);
491        pw.decreaseIndent();
492
493        pw.println("Active dialing, or connecting calls:");
494        pw.increaseIndent();
495        dumpCallsInCollection(pw, mActiveDialingOrConnectingCalls);
496        pw.decreaseIndent();
497
498        pw.println("Ringing calls:");
499        pw.increaseIndent();
500        dumpCallsInCollection(pw, mRingingCalls);
501        pw.decreaseIndent();
502
503        pw.println("Holding calls:");
504        pw.increaseIndent();
505        dumpCallsInCollection(pw, mHoldingCalls);
506        pw.decreaseIndent();
507
508        pw.println("Foreground call:");
509        pw.println(mForegroundCall);
510
511        pw.println("CallAudioModeStateMachine pending messages:");
512        pw.increaseIndent();
513        mCallAudioModeStateMachine.dumpPendingMessages(pw);
514        pw.decreaseIndent();
515
516        pw.println("CallAudioRouteStateMachine pending messages:");
517        pw.increaseIndent();
518        mCallAudioRouteStateMachine.dumpPendingMessages(pw);
519        pw.decreaseIndent();
520    }
521
522    @VisibleForTesting
523    public void setIsTonePlaying(boolean isTonePlaying) {
524        mIsTonePlaying = isTonePlaying;
525        mCallAudioModeStateMachine.sendMessageWithArgs(
526                isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING
527                        : CallAudioModeStateMachine.TONE_STOPPED_PLAYING,
528                makeArgsForModeStateMachine());
529
530        if (!isTonePlaying && mIsDisconnectedTonePlaying) {
531            mCallsManager.onDisconnectedTonePlaying(false);
532            mIsDisconnectedTonePlaying = false;
533        }
534    }
535
536    private void onCallLeavingState(Call call, int state) {
537        switch (state) {
538            case CallState.ACTIVE:
539            case CallState.CONNECTING:
540                onCallLeavingActiveDialingOrConnecting();
541                break;
542            case CallState.RINGING:
543                onCallLeavingRinging();
544                break;
545            case CallState.ON_HOLD:
546                onCallLeavingHold();
547                break;
548            case CallState.PULLING:
549                onCallLeavingActiveDialingOrConnecting();
550                break;
551            case CallState.DIALING:
552                stopRingbackForCall(call);
553                onCallLeavingActiveDialingOrConnecting();
554                break;
555        }
556    }
557
558    private void onCallEnteringState(Call call, int state) {
559        switch (state) {
560            case CallState.ACTIVE:
561            case CallState.CONNECTING:
562                onCallEnteringActiveDialingOrConnecting();
563                break;
564            case CallState.RINGING:
565                onCallEnteringRinging();
566                break;
567            case CallState.ON_HOLD:
568                onCallEnteringHold();
569                break;
570            case CallState.PULLING:
571                onCallEnteringActiveDialingOrConnecting();
572                break;
573            case CallState.DIALING:
574                onCallEnteringActiveDialingOrConnecting();
575                playRingbackForCall(call);
576                break;
577        }
578    }
579
580    private void onCallLeavingActiveDialingOrConnecting() {
581        if (mActiveDialingOrConnectingCalls.size() == 0) {
582            mCallAudioModeStateMachine.sendMessageWithArgs(
583                    CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS,
584                    makeArgsForModeStateMachine());
585        }
586    }
587
588    private void onCallLeavingRinging() {
589        if (mRingingCalls.size() == 0) {
590            mCallAudioModeStateMachine.sendMessageWithArgs(
591                    CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
592                    makeArgsForModeStateMachine());
593        }
594    }
595
596    private void onCallLeavingHold() {
597        if (mHoldingCalls.size() == 0) {
598            mCallAudioModeStateMachine.sendMessageWithArgs(
599                    CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS,
600                    makeArgsForModeStateMachine());
601        }
602    }
603
604    private void onCallEnteringActiveDialingOrConnecting() {
605        if (mActiveDialingOrConnectingCalls.size() == 1) {
606            mCallAudioModeStateMachine.sendMessageWithArgs(
607                    CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL,
608                    makeArgsForModeStateMachine());
609        }
610    }
611
612    private void onCallEnteringRinging() {
613        if (mRingingCalls.size() == 1) {
614            mCallAudioModeStateMachine.sendMessageWithArgs(
615                    CallAudioModeStateMachine.NEW_RINGING_CALL,
616                    makeArgsForModeStateMachine());
617        }
618    }
619
620    private void onCallEnteringHold() {
621        if (mHoldingCalls.size() == 1) {
622            mCallAudioModeStateMachine.sendMessageWithArgs(
623                    CallAudioModeStateMachine.NEW_HOLDING_CALL,
624                    makeArgsForModeStateMachine());
625        }
626    }
627
628    private void updateForegroundCall() {
629        Call oldForegroundCall = mForegroundCall;
630        if (mActiveDialingOrConnectingCalls.size() > 0) {
631            // Give preference for connecting calls over active/dialing for foreground-ness.
632            Call possibleConnectingCall = null;
633            for (Call call : mActiveDialingOrConnectingCalls) {
634                if (call.getState() == CallState.CONNECTING) {
635                    possibleConnectingCall = call;
636                }
637            }
638            mForegroundCall = possibleConnectingCall == null ?
639                    mActiveDialingOrConnectingCalls.iterator().next() : possibleConnectingCall;
640        } else if (mRingingCalls.size() > 0) {
641            mForegroundCall = mRingingCalls.iterator().next();
642        } else if (mHoldingCalls.size() > 0) {
643            mForegroundCall = mHoldingCalls.iterator().next();
644        } else {
645            mForegroundCall = null;
646        }
647
648        if (mForegroundCall != oldForegroundCall) {
649            mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
650                    CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
651            mDtmfLocalTonePlayer.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
652            maybePlayHoldTone();
653        }
654    }
655
656    @NonNull
657    private CallAudioModeStateMachine.MessageArgs makeArgsForModeStateMachine() {
658        return new CallAudioModeStateMachine.MessageArgs(
659                mActiveDialingOrConnectingCalls.size() > 0,
660                mRingingCalls.size() > 0,
661                mHoldingCalls.size() > 0,
662                mIsTonePlaying,
663                mForegroundCall != null && mForegroundCall.getIsVoipAudioMode(),
664                Log.createSubsession());
665    }
666
667    private void playToneForDisconnectedCall(Call call) {
668        // If this call is being disconnected as a result of being handed over to another call,
669        // we will not play a disconnect tone.
670        if (call.isHandoverInProgress()) {
671            Log.i(LOG_TAG, "Omitting tone because %s is being handed over.", call);
672            return;
673        }
674
675        if (mForegroundCall != null && call != mForegroundCall && mCalls.size() > 1) {
676            Log.v(LOG_TAG, "Omitting tone because we are not foreground" +
677                    " and there is another call.");
678            return;
679        }
680
681        if (call.getDisconnectCause() != null) {
682            int toneToPlay = InCallTonePlayer.TONE_INVALID;
683
684            Log.v(this, "Disconnect cause: %s.", call.getDisconnectCause());
685
686            switch(call.getDisconnectCause().getTone()) {
687                case ToneGenerator.TONE_SUP_BUSY:
688                    toneToPlay = InCallTonePlayer.TONE_BUSY;
689                    break;
690                case ToneGenerator.TONE_SUP_CONGESTION:
691                    toneToPlay = InCallTonePlayer.TONE_CONGESTION;
692                    break;
693                case ToneGenerator.TONE_CDMA_REORDER:
694                    toneToPlay = InCallTonePlayer.TONE_REORDER;
695                    break;
696                case ToneGenerator.TONE_CDMA_ABBR_INTERCEPT:
697                    toneToPlay = InCallTonePlayer.TONE_INTERCEPT;
698                    break;
699                case ToneGenerator.TONE_CDMA_CALLDROP_LITE:
700                    toneToPlay = InCallTonePlayer.TONE_CDMA_DROP;
701                    break;
702                case ToneGenerator.TONE_SUP_ERROR:
703                    toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER;
704                    break;
705                case ToneGenerator.TONE_PROP_PROMPT:
706                    toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
707                    break;
708            }
709
710            Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay);
711
712            if (toneToPlay != InCallTonePlayer.TONE_INVALID) {
713                mPlayerFactory.createPlayer(toneToPlay).startTone();
714                mCallsManager.onDisconnectedTonePlaying(true);
715                mIsDisconnectedTonePlaying = true;
716            }
717        }
718    }
719
720    private void playRingbackForCall(Call call) {
721        if (call == mForegroundCall && call.isRingbackRequested()) {
722            mRingbackPlayer.startRingbackForCall(call);
723        }
724    }
725
726    private void stopRingbackForCall(Call call) {
727        mRingbackPlayer.stopRingbackForCall(call);
728    }
729
730    /**
731     * Determines if a hold tone should be played and then starts or stops it accordingly.
732     */
733    private void maybePlayHoldTone() {
734        if (shouldPlayHoldTone()) {
735            if (mHoldTonePlayer == null) {
736                mHoldTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
737                mHoldTonePlayer.startTone();
738            }
739        } else {
740            if (mHoldTonePlayer != null) {
741                mHoldTonePlayer.stopTone();
742                mHoldTonePlayer = null;
743            }
744        }
745    }
746
747    /**
748     * Determines if a hold tone should be played.
749     * A hold tone should be played only if foreground call is equals with call which is
750     * remotely held.
751     *
752     * @return {@code true} if the the hold tone should be played, {@code false} otherwise.
753     */
754    private boolean shouldPlayHoldTone() {
755        Call foregroundCall = getForegroundCall();
756        // If there is no foreground call, no hold tone should play.
757        if (foregroundCall == null) {
758            return false;
759        }
760
761        // If another call is ringing, no hold tone should play.
762        if (mCallsManager.hasRingingCall()) {
763            return false;
764        }
765
766        // If the foreground call isn't active, no hold tone should play. This might happen, for
767        // example, if the user puts a remotely held call on hold itself.
768        if (!foregroundCall.isActive()) {
769            return false;
770        }
771
772        return foregroundCall.isRemotelyHeld();
773    }
774
775    private void dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls) {
776        for (Call call : calls) {
777            if (call != null) pw.println(call.getId());
778        }
779    }
780
781    private void maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(Call call) {
782        // Check to see if the call being answered/rejected is the only ringing call, since this
783        // will be called before the connection service acknowledges the state change.
784        if (mRingingCalls.size() == 0 ||
785                (mRingingCalls.size() == 1 && call == mRingingCalls.iterator().next())) {
786            mRinger.stopRinging();
787            mRinger.stopCallWaiting();
788        }
789    }
790
791    private boolean shouldPlayDisconnectTone(int oldState, int newState) {
792        if (newState != CallState.DISCONNECTED) {
793            return false;
794        }
795        return oldState == CallState.ACTIVE ||
796                oldState == CallState.DIALING ||
797                oldState == CallState.ON_HOLD;
798    }
799
800    @VisibleForTesting
801    public Set<Call> getTrackedCalls() {
802        return mCalls;
803    }
804
805    @VisibleForTesting
806    public SparseArray<LinkedHashSet<Call>> getCallStateToCalls() {
807        return mCallStateToCalls;
808    }
809}
810