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 
17 package com.android.server.telecom;
18 
19 import android.annotation.NonNull;
20 import android.media.IAudioService;
21 import android.media.ToneGenerator;
22 import android.telecom.CallAudioState;
23 import android.telecom.Log;
24 import android.telecom.VideoProfile;
25 import android.util.SparseArray;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 import com.android.internal.util.IndentingPrintWriter;
29 
30 import java.util.Collection;
31 import java.util.HashSet;
32 import java.util.Set;
33 import java.util.LinkedHashSet;
34 
35 public class CallAudioManager extends CallsManagerListenerBase {
36 
37     public interface AudioServiceFactory {
getAudioService()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 
CallAudioManager(CallAudioRouteStateMachine callAudioRouteStateMachine, CallsManager callsManager, CallAudioModeStateMachine callAudioModeStateMachine, InCallTonePlayer.Factory playerFactory, Ringer ringer, RingbackPlayer ringbackPlayer, DtmfLocalTonePlayer dtmfLocalTonePlayer)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
onCallStateChanged(Call call, int oldState, int newState)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
onCallAdded(Call call)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
onCallRemoved(Call call)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 
addCall(Call call)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 
removeCall(Call call)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
onExternalCallChanged(Call call, boolean isExternalCall)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      */
shouldIgnoreCallForAudio(Call call)207     private boolean shouldIgnoreCallForAudio(Call call) {
208         return call.getParentCall() != null || call.isExternalCall();
209     }
210 
211     @Override
onIncomingCallAnswered(Call call)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
onSessionModifyRequestReceived(Call call, VideoProfile videoProfile)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
onHoldToneRequested(Call call)274     public void onHoldToneRequested(Call call) {
275         maybePlayHoldTone();
276     }
277 
278     @Override
onIsVoipAudioModeChanged(Call call)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
onRingbackRequested(Call call, boolean shouldRingback)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
onIncomingCallRejected(Call call, boolean rejectWithMessage, String message)298     public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String message) {
299         maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call);
300     }
301 
302     @Override
onIsConferencedChanged(Call call)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
onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs, ConnectionServiceWrapper newCs)324     public void onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs,
325             ConnectionServiceWrapper newCs) {
326         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
327                 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
328     }
329 
330     @Override
onVideoStateChanged(Call call, int previousVideoState, int newVideoState)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 
getCallAudioState()349     public CallAudioState getCallAudioState() {
350         return mCallAudioRouteStateMachine.getCurrentCallAudioState();
351     }
352 
getPossiblyHeldForegroundCall()353     public Call getPossiblyHeldForegroundCall() {
354         return mForegroundCall;
355     }
356 
getForegroundCall()357     public Call getForegroundCall() {
358         if (mForegroundCall != null && mForegroundCall.getState() != CallState.ON_HOLD) {
359             return mForegroundCall;
360         }
361         return null;
362     }
363 
toggleMute()364     void toggleMute() {
365         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
366                 CallAudioRouteStateMachine.TOGGLE_MUTE);
367     }
368 
369     @VisibleForTesting
mute(boolean shouldMute)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      */
setAudioRoute(int route)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 
silenceRingers()416     void silenceRingers() {
417         for (Call call : mRingingCalls) {
418             call.silence();
419         }
420 
421         mRinger.stopRinging();
422         mRinger.stopCallWaiting();
423     }
424 
425     @VisibleForTesting
startRinging()426     public boolean startRinging() {
427         return mRinger.startRinging(mForegroundCall,
428                 mCallAudioRouteStateMachine.isHfpDeviceAvailable());
429     }
430 
431     @VisibleForTesting
startCallWaiting()432     public void startCallWaiting() {
433         mRinger.startCallWaiting(mRingingCalls.iterator().next());
434     }
435 
436     @VisibleForTesting
stopRinging()437     public void stopRinging() {
438         mRinger.stopRinging();
439     }
440 
441     @VisibleForTesting
stopCallWaiting()442     public void stopCallWaiting() {
443         mRinger.stopCallWaiting();
444     }
445 
446     @VisibleForTesting
setCallAudioRouteFocusState(int focusState)447     public void setCallAudioRouteFocusState(int focusState) {
448         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
449                 CallAudioRouteStateMachine.SWITCH_FOCUS, focusState);
450     }
451 
452     @VisibleForTesting
getCallAudioRouteStateMachine()453     public CallAudioRouteStateMachine getCallAudioRouteStateMachine() {
454         return mCallAudioRouteStateMachine;
455     }
456 
457     @VisibleForTesting
getCallAudioModeStateMachine()458     public CallAudioModeStateMachine getCallAudioModeStateMachine() {
459         return mCallAudioModeStateMachine;
460     }
461 
dump(IndentingPrintWriter pw)462     void dump(IndentingPrintWriter pw) {
463         pw.println("All calls:");
464         pw.increaseIndent();
465         dumpCallsInCollection(pw, mCalls);
466         pw.decreaseIndent();
467 
468         pw.println("Active dialing, or connecting calls:");
469         pw.increaseIndent();
470         dumpCallsInCollection(pw, mActiveDialingOrConnectingCalls);
471         pw.decreaseIndent();
472 
473         pw.println("Ringing calls:");
474         pw.increaseIndent();
475         dumpCallsInCollection(pw, mRingingCalls);
476         pw.decreaseIndent();
477 
478         pw.println("Holding calls:");
479         pw.increaseIndent();
480         dumpCallsInCollection(pw, mHoldingCalls);
481         pw.decreaseIndent();
482 
483         pw.println("Foreground call:");
484         pw.println(mForegroundCall);
485 
486         pw.println("CallAudioModeStateMachine pending messages:");
487         pw.increaseIndent();
488         mCallAudioModeStateMachine.dumpPendingMessages(pw);
489         pw.decreaseIndent();
490 
491         pw.println("CallAudioRouteStateMachine pending messages:");
492         pw.increaseIndent();
493         mCallAudioRouteStateMachine.dumpPendingMessages(pw);
494         pw.decreaseIndent();
495     }
496 
497     @VisibleForTesting
setIsTonePlaying(boolean isTonePlaying)498     public void setIsTonePlaying(boolean isTonePlaying) {
499         mIsTonePlaying = isTonePlaying;
500         mCallAudioModeStateMachine.sendMessageWithArgs(
501                 isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING
502                         : CallAudioModeStateMachine.TONE_STOPPED_PLAYING,
503                 makeArgsForModeStateMachine());
504     }
505 
onCallLeavingState(Call call, int state)506     private void onCallLeavingState(Call call, int state) {
507         switch (state) {
508             case CallState.ACTIVE:
509             case CallState.CONNECTING:
510                 onCallLeavingActiveDialingOrConnecting();
511                 break;
512             case CallState.RINGING:
513                 onCallLeavingRinging();
514                 break;
515             case CallState.ON_HOLD:
516                 onCallLeavingHold();
517                 break;
518             case CallState.PULLING:
519                 onCallLeavingActiveDialingOrConnecting();
520                 break;
521             case CallState.DIALING:
522                 stopRingbackForCall(call);
523                 onCallLeavingActiveDialingOrConnecting();
524                 break;
525         }
526     }
527 
onCallEnteringState(Call call, int state)528     private void onCallEnteringState(Call call, int state) {
529         switch (state) {
530             case CallState.ACTIVE:
531             case CallState.CONNECTING:
532                 onCallEnteringActiveDialingOrConnecting();
533                 break;
534             case CallState.RINGING:
535                 onCallEnteringRinging();
536                 break;
537             case CallState.ON_HOLD:
538                 onCallEnteringHold();
539                 break;
540             case CallState.PULLING:
541                 onCallEnteringActiveDialingOrConnecting();
542                 break;
543             case CallState.DIALING:
544                 onCallEnteringActiveDialingOrConnecting();
545                 playRingbackForCall(call);
546                 break;
547         }
548     }
549 
onCallLeavingActiveDialingOrConnecting()550     private void onCallLeavingActiveDialingOrConnecting() {
551         if (mActiveDialingOrConnectingCalls.size() == 0) {
552             mCallAudioModeStateMachine.sendMessageWithArgs(
553                     CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS,
554                     makeArgsForModeStateMachine());
555         }
556     }
557 
onCallLeavingRinging()558     private void onCallLeavingRinging() {
559         if (mRingingCalls.size() == 0) {
560             mCallAudioModeStateMachine.sendMessageWithArgs(
561                     CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
562                     makeArgsForModeStateMachine());
563         }
564     }
565 
onCallLeavingHold()566     private void onCallLeavingHold() {
567         if (mHoldingCalls.size() == 0) {
568             mCallAudioModeStateMachine.sendMessageWithArgs(
569                     CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS,
570                     makeArgsForModeStateMachine());
571         }
572     }
573 
onCallEnteringActiveDialingOrConnecting()574     private void onCallEnteringActiveDialingOrConnecting() {
575         if (mActiveDialingOrConnectingCalls.size() == 1) {
576             mCallAudioModeStateMachine.sendMessageWithArgs(
577                     CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL,
578                     makeArgsForModeStateMachine());
579         }
580     }
581 
onCallEnteringRinging()582     private void onCallEnteringRinging() {
583         if (mRingingCalls.size() == 1) {
584             mCallAudioModeStateMachine.sendMessageWithArgs(
585                     CallAudioModeStateMachine.NEW_RINGING_CALL,
586                     makeArgsForModeStateMachine());
587         }
588     }
589 
onCallEnteringHold()590     private void onCallEnteringHold() {
591         if (mHoldingCalls.size() == 1) {
592             mCallAudioModeStateMachine.sendMessageWithArgs(
593                     CallAudioModeStateMachine.NEW_HOLDING_CALL,
594                     makeArgsForModeStateMachine());
595         }
596     }
597 
updateForegroundCall()598     private void updateForegroundCall() {
599         Call oldForegroundCall = mForegroundCall;
600         if (mActiveDialingOrConnectingCalls.size() > 0) {
601             // Give preference for connecting calls over active/dialing for foreground-ness.
602             Call possibleConnectingCall = null;
603             for (Call call : mActiveDialingOrConnectingCalls) {
604                 if (call.getState() == CallState.CONNECTING) {
605                     possibleConnectingCall = call;
606                 }
607             }
608             mForegroundCall = possibleConnectingCall == null ?
609                     mActiveDialingOrConnectingCalls.iterator().next() : possibleConnectingCall;
610         } else if (mRingingCalls.size() > 0) {
611             mForegroundCall = mRingingCalls.iterator().next();
612         } else if (mHoldingCalls.size() > 0) {
613             mForegroundCall = mHoldingCalls.iterator().next();
614         } else {
615             mForegroundCall = null;
616         }
617 
618         if (mForegroundCall != oldForegroundCall) {
619             mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
620                     CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
621             mDtmfLocalTonePlayer.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
622             maybePlayHoldTone();
623         }
624     }
625 
626     @NonNull
makeArgsForModeStateMachine()627     private CallAudioModeStateMachine.MessageArgs makeArgsForModeStateMachine() {
628         return new CallAudioModeStateMachine.MessageArgs(
629                 mActiveDialingOrConnectingCalls.size() > 0,
630                 mRingingCalls.size() > 0,
631                 mHoldingCalls.size() > 0,
632                 mIsTonePlaying,
633                 mForegroundCall != null && mForegroundCall.getIsVoipAudioMode(),
634                 Log.createSubsession());
635     }
636 
playToneForDisconnectedCall(Call call)637     private void playToneForDisconnectedCall(Call call) {
638         if (mForegroundCall != null && call != mForegroundCall && mCalls.size() > 1) {
639             Log.v(LOG_TAG, "Omitting tone because we are not foreground" +
640                     " and there is another call.");
641             return;
642         }
643 
644         if (call.getDisconnectCause() != null) {
645             int toneToPlay = InCallTonePlayer.TONE_INVALID;
646 
647             Log.v(this, "Disconnect cause: %s.", call.getDisconnectCause());
648 
649             switch(call.getDisconnectCause().getTone()) {
650                 case ToneGenerator.TONE_SUP_BUSY:
651                     toneToPlay = InCallTonePlayer.TONE_BUSY;
652                     break;
653                 case ToneGenerator.TONE_SUP_CONGESTION:
654                     toneToPlay = InCallTonePlayer.TONE_CONGESTION;
655                     break;
656                 case ToneGenerator.TONE_CDMA_REORDER:
657                     toneToPlay = InCallTonePlayer.TONE_REORDER;
658                     break;
659                 case ToneGenerator.TONE_CDMA_ABBR_INTERCEPT:
660                     toneToPlay = InCallTonePlayer.TONE_INTERCEPT;
661                     break;
662                 case ToneGenerator.TONE_CDMA_CALLDROP_LITE:
663                     toneToPlay = InCallTonePlayer.TONE_CDMA_DROP;
664                     break;
665                 case ToneGenerator.TONE_SUP_ERROR:
666                     toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER;
667                     break;
668                 case ToneGenerator.TONE_PROP_PROMPT:
669                     toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
670                     break;
671             }
672 
673             Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay);
674 
675             if (toneToPlay != InCallTonePlayer.TONE_INVALID) {
676                 mPlayerFactory.createPlayer(toneToPlay).startTone();
677             }
678         }
679     }
680 
playRingbackForCall(Call call)681     private void playRingbackForCall(Call call) {
682         if (call == mForegroundCall && call.isRingbackRequested()) {
683             mRingbackPlayer.startRingbackForCall(call);
684         }
685     }
686 
stopRingbackForCall(Call call)687     private void stopRingbackForCall(Call call) {
688         mRingbackPlayer.stopRingbackForCall(call);
689     }
690 
691     /**
692      * Determines if a hold tone should be played and then starts or stops it accordingly.
693      */
maybePlayHoldTone()694     private void maybePlayHoldTone() {
695         if (shouldPlayHoldTone()) {
696             if (mHoldTonePlayer == null) {
697                 mHoldTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
698                 mHoldTonePlayer.startTone();
699             }
700         } else {
701             if (mHoldTonePlayer != null) {
702                 mHoldTonePlayer.stopTone();
703                 mHoldTonePlayer = null;
704             }
705         }
706     }
707 
708     /**
709      * Determines if a hold tone should be played.
710      * A hold tone should be played only if foreground call is equals with call which is
711      * remotely held.
712      *
713      * @return {@code true} if the the hold tone should be played, {@code false} otherwise.
714      */
shouldPlayHoldTone()715     private boolean shouldPlayHoldTone() {
716         Call foregroundCall = getForegroundCall();
717         // If there is no foreground call, no hold tone should play.
718         if (foregroundCall == null) {
719             return false;
720         }
721 
722         // If another call is ringing, no hold tone should play.
723         if (mCallsManager.hasRingingCall()) {
724             return false;
725         }
726 
727         // If the foreground call isn't active, no hold tone should play. This might happen, for
728         // example, if the user puts a remotely held call on hold itself.
729         if (!foregroundCall.isActive()) {
730             return false;
731         }
732 
733         return foregroundCall.isRemotelyHeld();
734     }
735 
dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls)736     private void dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls) {
737         for (Call call : calls) {
738             if (call != null) pw.println(call.getId());
739         }
740     }
741 
maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(Call call)742     private void maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(Call call) {
743         // Check to see if the call being answered/rejected is the only ringing call, since this
744         // will be called before the connection service acknowledges the state change.
745         if (mRingingCalls.size() == 0 ||
746                 (mRingingCalls.size() == 1 && call == mRingingCalls.iterator().next())) {
747             mRinger.stopRinging();
748             mRinger.stopCallWaiting();
749         }
750     }
751 
shouldPlayDisconnectTone(int oldState, int newState)752     private boolean shouldPlayDisconnectTone(int oldState, int newState) {
753         if (newState != CallState.DISCONNECTED) {
754             return false;
755         }
756         return oldState == CallState.ACTIVE ||
757                 oldState == CallState.DIALING ||
758                 oldState == CallState.ON_HOLD;
759     }
760 
761     @VisibleForTesting
getTrackedCalls()762     public Set<Call> getTrackedCalls() {
763         return mCalls;
764     }
765 
766     @VisibleForTesting
getCallStateToCalls()767     public SparseArray<LinkedHashSet<Call>> getCallStateToCalls() {
768         return mCallStateToCalls;
769     }
770 }