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