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