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 import com.android.server.telecom.CallAudioModeStateMachine.MessageArgs.Builder;
30 import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
31 
32 import java.util.Collection;
33 import java.util.HashSet;
34 import java.util.Set;
35 import java.util.LinkedHashSet;
36 
37 public class CallAudioManager extends CallsManagerListenerBase {
38 
39     public interface AudioServiceFactory {
40         IAudioService getAudioService();
41     }
42 
43     private final String LOG_TAG = CallAudioManager.class.getSimpleName();
44 
45     private final LinkedHashSet<Call> mActiveDialingOrConnectingCalls;
46     private final LinkedHashSet<Call> mRingingCalls;
47     private final LinkedHashSet<Call> mHoldingCalls;
48     private final LinkedHashSet<Call> mAudioProcessingCalls;
49     private final Set<Call> mCalls;
50     private final SparseArray<LinkedHashSet<Call>> mCallStateToCalls;
51 
52     private final CallAudioRouteStateMachine mCallAudioRouteStateMachine;
53     private final CallAudioModeStateMachine mCallAudioModeStateMachine;
54     private final BluetoothStateReceiver mBluetoothStateReceiver;
55     private final CallsManager mCallsManager;
56     private final InCallTonePlayer.Factory mPlayerFactory;
57     private final Ringer mRinger;
58     private final RingbackPlayer mRingbackPlayer;
59     private final DtmfLocalTonePlayer mDtmfLocalTonePlayer;
60 
61     private Call mForegroundCall;
62     private boolean mIsTonePlaying = false;
63     private boolean mIsDisconnectedTonePlaying = false;
64     private InCallTonePlayer mHoldTonePlayer;
65 
66     public CallAudioManager(CallAudioRouteStateMachine callAudioRouteStateMachine,
67             CallsManager callsManager,
68             CallAudioModeStateMachine callAudioModeStateMachine,
69             InCallTonePlayer.Factory playerFactory,
70             Ringer ringer,
71             RingbackPlayer ringbackPlayer,
72             BluetoothStateReceiver bluetoothStateReceiver,
73             DtmfLocalTonePlayer dtmfLocalTonePlayer) {
74         mActiveDialingOrConnectingCalls = new LinkedHashSet<>(1);
75         mRingingCalls = new LinkedHashSet<>(1);
76         mHoldingCalls = new LinkedHashSet<>(1);
77         mAudioProcessingCalls = new LinkedHashSet<>(1);
78         mCalls = new HashSet<>();
79         mCallStateToCalls = new SparseArray<LinkedHashSet<Call>>() {{
80             put(CallState.CONNECTING, mActiveDialingOrConnectingCalls);
81             put(CallState.ACTIVE, mActiveDialingOrConnectingCalls);
82             put(CallState.DIALING, mActiveDialingOrConnectingCalls);
83             put(CallState.PULLING, mActiveDialingOrConnectingCalls);
84             put(CallState.RINGING, mRingingCalls);
85             put(CallState.ON_HOLD, mHoldingCalls);
86             put(CallState.SIMULATED_RINGING, mRingingCalls);
87             put(CallState.AUDIO_PROCESSING, mAudioProcessingCalls);
88         }};
89 
90         mCallAudioRouteStateMachine = callAudioRouteStateMachine;
91         mCallAudioModeStateMachine = callAudioModeStateMachine;
92         mCallsManager = callsManager;
93         mPlayerFactory = playerFactory;
94         mRinger = ringer;
95         mRingbackPlayer = ringbackPlayer;
96         mBluetoothStateReceiver = bluetoothStateReceiver;
97         mDtmfLocalTonePlayer = dtmfLocalTonePlayer;
98 
99         mPlayerFactory.setCallAudioManager(this);
100         mCallAudioModeStateMachine.setCallAudioManager(this);
101         mCallAudioRouteStateMachine.setCallAudioManager(this);
102     }
103 
104     @Override
105     public void onCallStateChanged(Call call, int oldState, int newState) {
106         if (shouldIgnoreCallForAudio(call)) {
107             // No audio management for calls in a conference, or external calls.
108             return;
109         }
110         Log.d(LOG_TAG, "Call state changed for TC@%s: %s -> %s", call.getId(),
111                 CallState.toString(oldState), CallState.toString(newState));
112 
113         removeCallFromAllBins(call);
114         HashSet<Call> newBinForCall = getBinForCall(call);
115         if (newBinForCall != null) {
116             newBinForCall.add(call);
117         }
118         sendCallStatusToBluetoothStateReceiver();
119 
120         updateForegroundCall();
121         if (shouldPlayDisconnectTone(oldState, newState)) {
122             playToneForDisconnectedCall(call);
123         }
124 
125         onCallLeavingState(call, oldState);
126         onCallEnteringState(call, newState);
127     }
128 
129     @Override
130     public void onCallAdded(Call call) {
131         if (shouldIgnoreCallForAudio(call)) {
132             return; // Don't do audio handling for calls in a conference, or external calls.
133         }
134 
135         addCall(call);
136     }
137 
138     @Override
139     public void onCallRemoved(Call call) {
140         if (shouldIgnoreCallForAudio(call)) {
141             return; // Don't do audio handling for calls in a conference, or external calls.
142         }
143 
144         removeCall(call);
145     }
146 
147     private void addCall(Call call) {
148         if (mCalls.contains(call)) {
149             Log.w(LOG_TAG, "Call TC@%s is being added twice.", call.getId());
150             return; // No guarantees that the same call won't get added twice.
151         }
152 
153         Log.d(LOG_TAG, "Call added with id TC@%s in state %s", call.getId(),
154                 CallState.toString(call.getState()));
155 
156         HashSet<Call> newBinForCall = getBinForCall(call);
157         if (newBinForCall != null) {
158             newBinForCall.add(call);
159         }
160         updateForegroundCall();
161         mCalls.add(call);
162         sendCallStatusToBluetoothStateReceiver();
163 
164         onCallEnteringState(call, call.getState());
165     }
166 
167     private void removeCall(Call call) {
168         if (!mCalls.contains(call)) {
169             return; // No guarantees that the same call won't get removed twice.
170         }
171 
172         Log.d(LOG_TAG, "Call removed with id TC@%s in state %s", call.getId(),
173                 CallState.toString(call.getState()));
174 
175         removeCallFromAllBins(call);
176 
177         updateForegroundCall();
178         mCalls.remove(call);
179         sendCallStatusToBluetoothStateReceiver();
180 
181         onCallLeavingState(call, call.getState());
182     }
183 
184     private void sendCallStatusToBluetoothStateReceiver() {
185         // We're in a call if there are calls in mCalls that are not in mAudioProcessingCalls.
186         boolean isInCall = !mAudioProcessingCalls.containsAll(mCalls);
187         mBluetoothStateReceiver.setIsInCall(isInCall);
188     }
189 
190     /**
191      * Handles changes to the external state of a call.  External calls which become regular calls
192      * should be tracked, and regular calls which become external should no longer be tracked.
193      *
194      * @param call The call.
195      * @param isExternalCall {@code True} if the call is now external, {@code false} if it is now
196      *      a regular call.
197      */
198     @Override
199     public void onExternalCallChanged(Call call, boolean isExternalCall) {
200         if (isExternalCall) {
201             Log.d(LOG_TAG, "Removing call which became external ID %s", call.getId());
202             removeCall(call);
203         } else if (!isExternalCall) {
204             Log.d(LOG_TAG, "Adding external call which was pulled with ID %s", call.getId());
205             addCall(call);
206 
207             if (mCallsManager.isSpeakerphoneAutoEnabledForVideoCalls(call.getVideoState())) {
208                 // When pulling a video call, automatically enable the speakerphone.
209                 Log.d(LOG_TAG, "Switching to speaker because external video call %s was pulled." +
210                         call.getId());
211                 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
212                         CallAudioRouteStateMachine.SWITCH_SPEAKER);
213             }
214         }
215     }
216 
217     /**
218      * Determines if {@link CallAudioManager} should do any audio routing operations for a call.
219      * We ignore child calls of a conference and external calls for audio routing purposes.
220      *
221      * @param call The call to check.
222      * @return {@code true} if the call should be ignored for audio routing, {@code false}
223      * otherwise
224      */
225     private boolean shouldIgnoreCallForAudio(Call call) {
226         return call.getParentCall() != null || call.isExternalCall();
227     }
228 
229     @Override
230     public void onIncomingCallAnswered(Call call) {
231         if (!mCalls.contains(call)) {
232             return;
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
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     public void playRttUpgradeTone(Call call) {
268         if (call != mForegroundCall) {
269             // We only play tones for foreground calls.
270             return;
271         }
272         mPlayerFactory.createPlayer(InCallTonePlayer.TONE_RTT_REQUEST).startTone();
273     }
274 
275     /**
276      * Play or stop a call hold tone for a call.  Triggered via
277      * {@link Connection#sendConnectionEvent(String)} when the
278      * {@link Connection#EVENT_ON_HOLD_TONE_START} event or
279      * {@link Connection#EVENT_ON_HOLD_TONE_STOP} event is passed through to the
280      *
281      * @param call The call which requested the hold tone.
282      */
283     @Override
284     public void onHoldToneRequested(Call call) {
285         maybePlayHoldTone();
286     }
287 
288     @Override
289     public void onIsVoipAudioModeChanged(Call call) {
290         if (call != mForegroundCall) {
291             return;
292         }
293         mCallAudioModeStateMachine.sendMessageWithArgs(
294                 CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE,
295                 makeArgsForModeStateMachine());
296     }
297 
298     @Override
299     public void onRingbackRequested(Call call, boolean shouldRingback) {
300         if (call == mForegroundCall && shouldRingback) {
301             mRingbackPlayer.startRingbackForCall(call);
302         } else {
303             mRingbackPlayer.stopRingbackForCall(call);
304         }
305     }
306 
307     @Override
308     public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String message) {
309         maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call);
310     }
311 
312     @Override
313     public void onIsConferencedChanged(Call call) {
314         // This indicates a conferencing change, which shouldn't impact any audio mode stuff.
315         Call parentCall = call.getParentCall();
316         if (parentCall == null) {
317             // Indicates that the call should be tracked for audio purposes. Treat it as if it were
318             // just added.
319             Log.i(LOG_TAG, "Call TC@" + call.getId() + " left conference and will" +
320                             " now be tracked by CallAudioManager.");
321             onCallAdded(call);
322         } else {
323             // The call joined a conference, so stop tracking it.
324             removeCallFromAllBins(call);
325             updateForegroundCall();
326             mCalls.remove(call);
327         }
328     }
329 
330     @Override
331     public void onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs,
332             ConnectionServiceWrapper newCs) {
333         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
334                 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
335     }
336 
337     @Override
338     public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {
339         if (call != getForegroundCall()) {
340             Log.d(LOG_TAG, "Ignoring video state change from %s to %s for call %s -- not " +
341                     "foreground.", VideoProfile.videoStateToString(previousVideoState),
342                     VideoProfile.videoStateToString(newVideoState), call.getId());
343             return;
344         }
345 
346         if (!VideoProfile.isVideo(previousVideoState) &&
347                 mCallsManager.isSpeakerphoneAutoEnabledForVideoCalls(newVideoState)) {
348             Log.d(LOG_TAG, "Switching to speaker because call %s transitioned video state from %s" +
349                     " to %s", call.getId(), VideoProfile.videoStateToString(previousVideoState),
350                     VideoProfile.videoStateToString(newVideoState));
351             mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
352                     CallAudioRouteStateMachine.SWITCH_SPEAKER);
353         }
354     }
355 
356     public CallAudioState getCallAudioState() {
357         return mCallAudioRouteStateMachine.getCurrentCallAudioState();
358     }
359 
360     public Call getPossiblyHeldForegroundCall() {
361         return mForegroundCall;
362     }
363 
364     public Call getForegroundCall() {
365         if (mForegroundCall != null && mForegroundCall.getState() != CallState.ON_HOLD) {
366             return mForegroundCall;
367         }
368         return null;
369     }
370 
371     @VisibleForTesting
372     public void toggleMute() {
373         // Don't mute if there are any emergency calls.
374         if (mCallsManager.isInEmergencyCall()) {
375             Log.v(this, "ignoring toggleMute for emergency call");
376             return;
377         }
378         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
379                 CallAudioRouteStateMachine.TOGGLE_MUTE);
380     }
381 
382     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
383     public void onRingerModeChange() {
384         mCallAudioModeStateMachine.sendMessageWithArgs(
385                 CallAudioModeStateMachine.RINGER_MODE_CHANGE, makeArgsForModeStateMachine());
386     }
387 
388     @VisibleForTesting
389     public void mute(boolean shouldMute) {
390         Log.v(this, "mute, shouldMute: %b", shouldMute);
391 
392         // Don't mute if there are any emergency calls.
393         if (mCallsManager.isInEmergencyCall()) {
394             shouldMute = false;
395             Log.v(this, "ignoring mute for emergency call");
396         }
397 
398         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(shouldMute
399                 ? CallAudioRouteStateMachine.MUTE_ON : CallAudioRouteStateMachine.MUTE_OFF);
400     }
401 
402     /**
403      * Changed the audio route, for example from earpiece to speaker phone.
404      *
405      * @param route The new audio route to use. See {@link CallAudioState}.
406      * @param bluetoothAddress the address of the desired bluetooth device, if route is
407      * {@link CallAudioState#ROUTE_BLUETOOTH}.
408      */
409     void setAudioRoute(int route, String bluetoothAddress) {
410         Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route));
411         switch (route) {
412             case CallAudioState.ROUTE_BLUETOOTH:
413                 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
414                         CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH, 0, bluetoothAddress);
415                 return;
416             case CallAudioState.ROUTE_SPEAKER:
417                 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
418                         CallAudioRouteStateMachine.USER_SWITCH_SPEAKER);
419                 return;
420             case CallAudioState.ROUTE_WIRED_HEADSET:
421                 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
422                         CallAudioRouteStateMachine.USER_SWITCH_HEADSET);
423                 return;
424             case CallAudioState.ROUTE_EARPIECE:
425                 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
426                         CallAudioRouteStateMachine.USER_SWITCH_EARPIECE);
427                 return;
428             case CallAudioState.ROUTE_WIRED_OR_EARPIECE:
429                 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
430                         CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE,
431                         CallAudioRouteStateMachine.NO_INCLUDE_BLUETOOTH_IN_BASELINE);
432                 return;
433             default:
434                 Log.w(this, "InCallService requested an invalid audio route: %d", route);
435         }
436     }
437 
438     /**
439      * Switch call audio routing to the baseline route, including bluetooth headsets if there are
440      * any connected.
441      */
442     void switchBaseline() {
443         Log.i(this, "switchBaseline");
444         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
445                 CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE,
446                 CallAudioRouteStateMachine.INCLUDE_BLUETOOTH_IN_BASELINE);
447     }
448 
449     void silenceRingers() {
450         synchronized (mCallsManager.getLock()) {
451             for (Call call : mRingingCalls) {
452                 call.silence();
453             }
454 
455             mRinger.stopRinging();
456             mRinger.stopCallWaiting();
457         }
458     }
459 
460     public boolean isRingtonePlaying() {
461         return mRinger.isRinging();
462     }
463 
464     @VisibleForTesting
465     public boolean startRinging() {
466         synchronized (mCallsManager.getLock()) {
467             return mRinger.startRinging(mForegroundCall,
468                     mCallAudioRouteStateMachine.isHfpDeviceAvailable());
469         }
470     }
471 
472     @VisibleForTesting
473     public void startCallWaiting(String reason) {
474         synchronized (mCallsManager.getLock()) {
475             if (mRingingCalls.size() == 1) {
476                 mRinger.startCallWaiting(mRingingCalls.iterator().next(), reason);
477             }
478         }
479     }
480 
481     @VisibleForTesting
482     public void stopRinging() {
483         synchronized (mCallsManager.getLock()) {
484             mRinger.stopRinging();
485         }
486     }
487 
488     @VisibleForTesting
489     public void stopCallWaiting() {
490         synchronized (mCallsManager.getLock()) {
491             mRinger.stopCallWaiting();
492         }
493     }
494 
495     @VisibleForTesting
496     public void setCallAudioRouteFocusState(int focusState) {
497         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
498                 CallAudioRouteStateMachine.SWITCH_FOCUS, focusState);
499     }
500 
501     public void notifyAudioOperationsComplete() {
502         mCallAudioModeStateMachine.sendMessageWithArgs(
503                 CallAudioModeStateMachine.AUDIO_OPERATIONS_COMPLETE, makeArgsForModeStateMachine());
504     }
505 
506     @VisibleForTesting
507     public CallAudioRouteStateMachine getCallAudioRouteStateMachine() {
508         return mCallAudioRouteStateMachine;
509     }
510 
511     @VisibleForTesting
512     public CallAudioModeStateMachine getCallAudioModeStateMachine() {
513         return mCallAudioModeStateMachine;
514     }
515 
516     void dump(IndentingPrintWriter pw) {
517         pw.println("All calls:");
518         pw.increaseIndent();
519         dumpCallsInCollection(pw, mCalls);
520         pw.decreaseIndent();
521 
522         pw.println("Active dialing, or connecting calls:");
523         pw.increaseIndent();
524         dumpCallsInCollection(pw, mActiveDialingOrConnectingCalls);
525         pw.decreaseIndent();
526 
527         pw.println("Ringing calls:");
528         pw.increaseIndent();
529         dumpCallsInCollection(pw, mRingingCalls);
530         pw.decreaseIndent();
531 
532         pw.println("Holding calls:");
533         pw.increaseIndent();
534         dumpCallsInCollection(pw, mHoldingCalls);
535         pw.decreaseIndent();
536 
537         pw.println("Foreground call:");
538         pw.println(mForegroundCall);
539 
540         pw.println("CallAudioModeStateMachine pending messages:");
541         pw.increaseIndent();
542         mCallAudioModeStateMachine.dumpPendingMessages(pw);
543         pw.decreaseIndent();
544 
545         pw.println("CallAudioRouteStateMachine pending messages:");
546         pw.increaseIndent();
547         mCallAudioRouteStateMachine.dumpPendingMessages(pw);
548         pw.decreaseIndent();
549 
550         pw.println("BluetoothDeviceManager:");
551         pw.increaseIndent();
552         if (mBluetoothStateReceiver.getBluetoothDeviceManager() != null) {
553             mBluetoothStateReceiver.getBluetoothDeviceManager().dump(pw);
554         }
555         pw.decreaseIndent();
556     }
557 
558     @VisibleForTesting
559     public void setIsTonePlaying(boolean isTonePlaying) {
560         mIsTonePlaying = isTonePlaying;
561         mCallAudioModeStateMachine.sendMessageWithArgs(
562                 isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING
563                         : CallAudioModeStateMachine.TONE_STOPPED_PLAYING,
564                 makeArgsForModeStateMachine());
565 
566         if (!isTonePlaying && mIsDisconnectedTonePlaying) {
567             mCallsManager.onDisconnectedTonePlaying(false);
568             mIsDisconnectedTonePlaying = false;
569         }
570     }
571 
572     private void onCallLeavingState(Call call, int state) {
573         switch (state) {
574             case CallState.ACTIVE:
575             case CallState.CONNECTING:
576                 onCallLeavingActiveDialingOrConnecting();
577                 break;
578             case CallState.RINGING:
579             case CallState.SIMULATED_RINGING:
580             case CallState.ANSWERED:
581                 onCallLeavingRinging();
582                 break;
583             case CallState.ON_HOLD:
584                 onCallLeavingHold();
585                 break;
586             case CallState.PULLING:
587                 onCallLeavingActiveDialingOrConnecting();
588                 break;
589             case CallState.DIALING:
590                 stopRingbackForCall(call);
591                 onCallLeavingActiveDialingOrConnecting();
592                 break;
593             case CallState.AUDIO_PROCESSING:
594                 onCallLeavingAudioProcessing();
595                 break;
596         }
597     }
598 
599     private void onCallEnteringState(Call call, int state) {
600         switch (state) {
601             case CallState.ACTIVE:
602             case CallState.CONNECTING:
603                 onCallEnteringActiveDialingOrConnecting();
604                 break;
605             case CallState.RINGING:
606             case CallState.SIMULATED_RINGING:
607                 onCallEnteringRinging();
608                 break;
609             case CallState.ON_HOLD:
610                 onCallEnteringHold();
611                 break;
612             case CallState.PULLING:
613                 onCallEnteringActiveDialingOrConnecting();
614                 break;
615             case CallState.DIALING:
616                 onCallEnteringActiveDialingOrConnecting();
617                 playRingbackForCall(call);
618                 break;
619             case CallState.ANSWERED:
620                 if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) {
621                     onCallEnteringActiveDialingOrConnecting();
622                 }
623                 break;
624             case CallState.AUDIO_PROCESSING:
625                 onCallEnteringAudioProcessing();
626                 break;
627         }
628     }
629 
630     private void onCallLeavingAudioProcessing() {
631         if (mAudioProcessingCalls.size() == 0) {
632             mCallAudioModeStateMachine.sendMessageWithArgs(
633                     CallAudioModeStateMachine.NO_MORE_AUDIO_PROCESSING_CALLS,
634                     makeArgsForModeStateMachine());
635         }
636     }
637 
638     private void onCallEnteringAudioProcessing() {
639         if (mAudioProcessingCalls.size() == 1) {
640             mCallAudioModeStateMachine.sendMessageWithArgs(
641                     CallAudioModeStateMachine.NEW_AUDIO_PROCESSING_CALL,
642                     makeArgsForModeStateMachine());
643         }
644     }
645 
646     private void onCallLeavingActiveDialingOrConnecting() {
647         if (mActiveDialingOrConnectingCalls.size() == 0) {
648             mCallAudioModeStateMachine.sendMessageWithArgs(
649                     CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS,
650                     makeArgsForModeStateMachine());
651         }
652     }
653 
654     private void onCallLeavingRinging() {
655         if (mRingingCalls.size() == 0) {
656             mCallAudioModeStateMachine.sendMessageWithArgs(
657                     CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
658                     makeArgsForModeStateMachine());
659         }
660     }
661 
662     private void onCallLeavingHold() {
663         if (mHoldingCalls.size() == 0) {
664             mCallAudioModeStateMachine.sendMessageWithArgs(
665                     CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS,
666                     makeArgsForModeStateMachine());
667         }
668     }
669 
670     private void onCallEnteringActiveDialingOrConnecting() {
671         if (mActiveDialingOrConnectingCalls.size() == 1) {
672             mCallAudioModeStateMachine.sendMessageWithArgs(
673                     CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL,
674                     makeArgsForModeStateMachine());
675         }
676     }
677 
678     private void onCallEnteringRinging() {
679         if (mRingingCalls.size() == 1) {
680             mCallAudioModeStateMachine.sendMessageWithArgs(
681                     CallAudioModeStateMachine.NEW_RINGING_CALL,
682                     makeArgsForModeStateMachine());
683         }
684     }
685 
686     private void onCallEnteringHold() {
687         if (mHoldingCalls.size() == 1) {
688             mCallAudioModeStateMachine.sendMessageWithArgs(
689                     CallAudioModeStateMachine.NEW_HOLDING_CALL,
690                     makeArgsForModeStateMachine());
691         }
692     }
693 
694     private void updateForegroundCall() {
695         Call oldForegroundCall = mForegroundCall;
696         if (mActiveDialingOrConnectingCalls.size() > 0) {
697             // Give preference for connecting calls over active/dialing for foreground-ness.
698             Call possibleConnectingCall = null;
699             for (Call call : mActiveDialingOrConnectingCalls) {
700                 if (call.getState() == CallState.CONNECTING) {
701                     possibleConnectingCall = call;
702                 }
703             }
704             mForegroundCall = possibleConnectingCall == null ?
705                     mActiveDialingOrConnectingCalls.iterator().next() : possibleConnectingCall;
706         } else if (mRingingCalls.size() > 0) {
707             mForegroundCall = mRingingCalls.iterator().next();
708         } else if (mHoldingCalls.size() > 0) {
709             mForegroundCall = mHoldingCalls.iterator().next();
710         } else {
711             mForegroundCall = null;
712         }
713 
714         if (mForegroundCall != oldForegroundCall) {
715             mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
716                     CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
717             mDtmfLocalTonePlayer.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
718             maybePlayHoldTone();
719         }
720     }
721 
722     @NonNull
723     private CallAudioModeStateMachine.MessageArgs makeArgsForModeStateMachine() {
724         return new Builder()
725                 .setHasActiveOrDialingCalls(mActiveDialingOrConnectingCalls.size() > 0)
726                 .setHasRingingCalls(mRingingCalls.size() > 0)
727                 .setHasHoldingCalls(mHoldingCalls.size() > 0)
728                 .setHasAudioProcessingCalls(mAudioProcessingCalls.size() > 0)
729                 .setIsTonePlaying(mIsTonePlaying)
730                 .setForegroundCallIsVoip(
731                         mForegroundCall != null && mForegroundCall.getIsVoipAudioMode())
732                 .setSession(Log.createSubsession()).build();
733     }
734 
735     private HashSet<Call> getBinForCall(Call call) {
736         if (call.getState() == CallState.ANSWERED) {
737             // If the call has the speed-up-mt-audio capability, treat answered state as active
738             // for audio purposes.
739             if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) {
740                 return mActiveDialingOrConnectingCalls;
741             }
742             return mRingingCalls;
743         }
744         return mCallStateToCalls.get(call.getState());
745     }
746 
747     private void removeCallFromAllBins(Call call) {
748         for (int i = 0; i < mCallStateToCalls.size(); i++) {
749             mCallStateToCalls.valueAt(i).remove(call);
750         }
751     }
752 
753     private void playToneForDisconnectedCall(Call call) {
754         // If this call is being disconnected as a result of being handed over to another call,
755         // we will not play a disconnect tone.
756         if (call.isHandoverInProgress()) {
757             Log.i(LOG_TAG, "Omitting tone because %s is being handed over.", call);
758             return;
759         }
760 
761         if (mForegroundCall != null && call != mForegroundCall && mCalls.size() > 1) {
762             Log.v(LOG_TAG, "Omitting tone because we are not foreground" +
763                     " and there is another call.");
764             return;
765         }
766 
767         if (call.getDisconnectCause() != null) {
768             int toneToPlay = InCallTonePlayer.TONE_INVALID;
769 
770             Log.v(this, "Disconnect cause: %s.", call.getDisconnectCause());
771 
772             switch(call.getDisconnectCause().getTone()) {
773                 case ToneGenerator.TONE_SUP_BUSY:
774                     toneToPlay = InCallTonePlayer.TONE_BUSY;
775                     break;
776                 case ToneGenerator.TONE_SUP_CONGESTION:
777                     toneToPlay = InCallTonePlayer.TONE_CONGESTION;
778                     break;
779                 case ToneGenerator.TONE_CDMA_REORDER:
780                     toneToPlay = InCallTonePlayer.TONE_REORDER;
781                     break;
782                 case ToneGenerator.TONE_CDMA_ABBR_INTERCEPT:
783                     toneToPlay = InCallTonePlayer.TONE_INTERCEPT;
784                     break;
785                 case ToneGenerator.TONE_CDMA_CALLDROP_LITE:
786                     toneToPlay = InCallTonePlayer.TONE_CDMA_DROP;
787                     break;
788                 case ToneGenerator.TONE_SUP_ERROR:
789                     toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER;
790                     break;
791                 case ToneGenerator.TONE_PROP_PROMPT:
792                     toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
793                     break;
794             }
795 
796             Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay);
797 
798             if (toneToPlay != InCallTonePlayer.TONE_INVALID) {
799                 boolean didToneStart = mPlayerFactory.createPlayer(toneToPlay).startTone();
800                 if (didToneStart) {
801                     mCallsManager.onDisconnectedTonePlaying(true);
802                     mIsDisconnectedTonePlaying = true;
803                 }
804             }
805         }
806     }
807 
808     private void playRingbackForCall(Call call) {
809         if (call == mForegroundCall && call.isRingbackRequested()) {
810             mRingbackPlayer.startRingbackForCall(call);
811         }
812     }
813 
814     private void stopRingbackForCall(Call call) {
815         mRingbackPlayer.stopRingbackForCall(call);
816     }
817 
818     /**
819      * Determines if a hold tone should be played and then starts or stops it accordingly.
820      */
821     private void maybePlayHoldTone() {
822         if (shouldPlayHoldTone()) {
823             if (mHoldTonePlayer == null) {
824                 mHoldTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
825                 mHoldTonePlayer.startTone();
826             }
827         } else {
828             if (mHoldTonePlayer != null) {
829                 mHoldTonePlayer.stopTone();
830                 mHoldTonePlayer = null;
831             }
832         }
833     }
834 
835     /**
836      * Determines if a hold tone should be played.
837      * A hold tone should be played only if foreground call is equals with call which is
838      * remotely held.
839      *
840      * @return {@code true} if the the hold tone should be played, {@code false} otherwise.
841      */
842     private boolean shouldPlayHoldTone() {
843         Call foregroundCall = getForegroundCall();
844         // If there is no foreground call, no hold tone should play.
845         if (foregroundCall == null) {
846             return false;
847         }
848 
849         // If another call is ringing, no hold tone should play.
850         if (mCallsManager.hasRingingCall()) {
851             return false;
852         }
853 
854         // If the foreground call isn't active, no hold tone should play. This might happen, for
855         // example, if the user puts a remotely held call on hold itself.
856         if (!foregroundCall.isActive()) {
857             return false;
858         }
859 
860         return foregroundCall.isRemotelyHeld();
861     }
862 
863     private void dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls) {
864         for (Call call : calls) {
865             if (call != null) pw.println(call.getId());
866         }
867     }
868 
869     private void maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(Call call) {
870         // Check to see if the call being answered/rejected is the only ringing call, since this
871         // will be called before the connection service acknowledges the state change.
872         synchronized (mCallsManager.getLock()) {
873             if (mRingingCalls.size() == 0 ||
874                     (mRingingCalls.size() == 1 && call == mRingingCalls.iterator().next())) {
875                 mRinger.stopRinging();
876                 mRinger.stopCallWaiting();
877             }
878         }
879     }
880 
881     private boolean shouldPlayDisconnectTone(int oldState, int newState) {
882         if (newState != CallState.DISCONNECTED) {
883             return false;
884         }
885         return oldState == CallState.ACTIVE ||
886                 oldState == CallState.DIALING ||
887                 oldState == CallState.ON_HOLD;
888     }
889 
890     @VisibleForTesting
891     public Set<Call> getTrackedCalls() {
892         return mCalls;
893     }
894 
895     @VisibleForTesting
896     public SparseArray<LinkedHashSet<Call>> getCallStateToCalls() {
897         return mCallStateToCalls;
898     }
899 }
900