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