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