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