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 {
getAudioService()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 
CallAudioManager(CallAudioRouteStateMachine callAudioRouteStateMachine, CallsManager callsManager, CallAudioModeStateMachine callAudioModeStateMachine, InCallTonePlayer.Factory playerFactory, Ringer ringer, RingbackPlayer ringbackPlayer, BluetoothStateReceiver bluetoothStateReceiver, DtmfLocalTonePlayer dtmfLocalTonePlayer)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
onCallStateChanged(Call call, int oldState, int newState)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
onCallAdded(Call call)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
onCallRemoved(Call call)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 
addCall(Call call)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 
removeCall(Call call)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 
sendCallStatusToBluetoothStateReceiver()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
onExternalCallChanged(Call call, boolean isExternalCall)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      */
shouldIgnoreCallForAudio(Call call)225     private boolean shouldIgnoreCallForAudio(Call call) {
226         return call.getParentCall() != null || call.isExternalCall();
227     }
228 
229     @Override
onIncomingCallAnswered(Call call)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
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             removeCallFromAllBins(call);
317             updateForegroundCall();
318             mCalls.remove(call);
319         }
320     }
321 
322     @Override
onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs, ConnectionServiceWrapper newCs)323     public void onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs,
324             ConnectionServiceWrapper newCs) {
325         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
326                 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
327     }
328 
329     @Override
onVideoStateChanged(Call call, int previousVideoState, int newVideoState)330     public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {
331         if (call != getForegroundCall()) {
332             Log.d(LOG_TAG, "Ignoring video state change from %s to %s for call %s -- not " +
333                     "foreground.", VideoProfile.videoStateToString(previousVideoState),
334                     VideoProfile.videoStateToString(newVideoState), call.getId());
335             return;
336         }
337 
338         if (!VideoProfile.isVideo(previousVideoState) &&
339                 mCallsManager.isSpeakerphoneAutoEnabledForVideoCalls(newVideoState)) {
340             Log.d(LOG_TAG, "Switching to speaker because call %s transitioned video state from %s" +
341                     " to %s", call.getId(), VideoProfile.videoStateToString(previousVideoState),
342                     VideoProfile.videoStateToString(newVideoState));
343             mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
344                     CallAudioRouteStateMachine.SWITCH_SPEAKER);
345         }
346     }
347 
getCallAudioState()348     public CallAudioState getCallAudioState() {
349         return mCallAudioRouteStateMachine.getCurrentCallAudioState();
350     }
351 
getPossiblyHeldForegroundCall()352     public Call getPossiblyHeldForegroundCall() {
353         return mForegroundCall;
354     }
355 
getForegroundCall()356     public Call getForegroundCall() {
357         if (mForegroundCall != null && mForegroundCall.getState() != CallState.ON_HOLD) {
358             return mForegroundCall;
359         }
360         return null;
361     }
362 
363     @VisibleForTesting
toggleMute()364     public void toggleMute() {
365         // Don't mute if there are any emergency calls.
366         if (mCallsManager.isInEmergencyCall()) {
367             Log.v(this, "ignoring toggleMute for emergency call");
368             return;
369         }
370         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
371                 CallAudioRouteStateMachine.TOGGLE_MUTE);
372     }
373 
374     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
onRingerModeChange()375     public void onRingerModeChange() {
376         mCallAudioModeStateMachine.sendMessageWithArgs(
377                 CallAudioModeStateMachine.RINGER_MODE_CHANGE, makeArgsForModeStateMachine());
378     }
379 
380     @VisibleForTesting
mute(boolean shouldMute)381     public void mute(boolean shouldMute) {
382         Log.v(this, "mute, shouldMute: %b", shouldMute);
383 
384         // Don't mute if there are any emergency calls.
385         if (mCallsManager.isInEmergencyCall()) {
386             shouldMute = false;
387             Log.v(this, "ignoring mute for emergency call");
388         }
389 
390         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(shouldMute
391                 ? CallAudioRouteStateMachine.MUTE_ON : CallAudioRouteStateMachine.MUTE_OFF);
392     }
393 
394     /**
395      * Changed the audio route, for example from earpiece to speaker phone.
396      *
397      * @param route The new audio route to use. See {@link CallAudioState}.
398      * @param bluetoothAddress the address of the desired bluetooth device, if route is
399      * {@link CallAudioState#ROUTE_BLUETOOTH}.
400      */
setAudioRoute(int route, String bluetoothAddress)401     void setAudioRoute(int route, String bluetoothAddress) {
402         Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route));
403         switch (route) {
404             case CallAudioState.ROUTE_BLUETOOTH:
405                 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
406                         CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH, 0, bluetoothAddress);
407                 return;
408             case CallAudioState.ROUTE_SPEAKER:
409                 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
410                         CallAudioRouteStateMachine.USER_SWITCH_SPEAKER);
411                 return;
412             case CallAudioState.ROUTE_WIRED_HEADSET:
413                 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
414                         CallAudioRouteStateMachine.USER_SWITCH_HEADSET);
415                 return;
416             case CallAudioState.ROUTE_EARPIECE:
417                 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
418                         CallAudioRouteStateMachine.USER_SWITCH_EARPIECE);
419                 return;
420             case CallAudioState.ROUTE_WIRED_OR_EARPIECE:
421                 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
422                         CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE,
423                         CallAudioRouteStateMachine.NO_INCLUDE_BLUETOOTH_IN_BASELINE);
424                 return;
425             default:
426                 Log.w(this, "InCallService requested an invalid audio route: %d", route);
427         }
428     }
429 
430     /**
431      * Switch call audio routing to the baseline route, including bluetooth headsets if there are
432      * any connected.
433      */
switchBaseline()434     void switchBaseline() {
435         Log.i(this, "switchBaseline");
436         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
437                 CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE,
438                 CallAudioRouteStateMachine.INCLUDE_BLUETOOTH_IN_BASELINE);
439     }
440 
silenceRingers()441     void silenceRingers() {
442         synchronized (mCallsManager.getLock()) {
443             for (Call call : mRingingCalls) {
444                 call.silence();
445             }
446 
447             mRinger.stopRinging();
448             mRinger.stopCallWaiting();
449         }
450     }
451 
isRingtonePlaying()452     public boolean isRingtonePlaying() {
453         return mRinger.isRinging();
454     }
455 
456     @VisibleForTesting
startRinging()457     public boolean startRinging() {
458         synchronized (mCallsManager.getLock()) {
459             return mRinger.startRinging(mForegroundCall,
460                     mCallAudioRouteStateMachine.isHfpDeviceAvailable());
461         }
462     }
463 
464     @VisibleForTesting
startCallWaiting(String reason)465     public void startCallWaiting(String reason) {
466         synchronized (mCallsManager.getLock()) {
467             if (mRingingCalls.size() == 1) {
468                 mRinger.startCallWaiting(mRingingCalls.iterator().next(), reason);
469             }
470         }
471     }
472 
473     @VisibleForTesting
stopRinging()474     public void stopRinging() {
475         synchronized (mCallsManager.getLock()) {
476             mRinger.stopRinging();
477         }
478     }
479 
480     @VisibleForTesting
stopCallWaiting()481     public void stopCallWaiting() {
482         synchronized (mCallsManager.getLock()) {
483             mRinger.stopCallWaiting();
484         }
485     }
486 
487     @VisibleForTesting
setCallAudioRouteFocusState(int focusState)488     public void setCallAudioRouteFocusState(int focusState) {
489         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
490                 CallAudioRouteStateMachine.SWITCH_FOCUS, focusState);
491     }
492 
notifyAudioOperationsComplete()493     public void notifyAudioOperationsComplete() {
494         mCallAudioModeStateMachine.sendMessageWithArgs(
495                 CallAudioModeStateMachine.AUDIO_OPERATIONS_COMPLETE, makeArgsForModeStateMachine());
496     }
497 
498     @VisibleForTesting
getCallAudioRouteStateMachine()499     public CallAudioRouteStateMachine getCallAudioRouteStateMachine() {
500         return mCallAudioRouteStateMachine;
501     }
502 
503     @VisibleForTesting
getCallAudioModeStateMachine()504     public CallAudioModeStateMachine getCallAudioModeStateMachine() {
505         return mCallAudioModeStateMachine;
506     }
507 
dump(IndentingPrintWriter pw)508     void dump(IndentingPrintWriter pw) {
509         pw.println("All calls:");
510         pw.increaseIndent();
511         dumpCallsInCollection(pw, mCalls);
512         pw.decreaseIndent();
513 
514         pw.println("Active dialing, or connecting calls:");
515         pw.increaseIndent();
516         dumpCallsInCollection(pw, mActiveDialingOrConnectingCalls);
517         pw.decreaseIndent();
518 
519         pw.println("Ringing calls:");
520         pw.increaseIndent();
521         dumpCallsInCollection(pw, mRingingCalls);
522         pw.decreaseIndent();
523 
524         pw.println("Holding calls:");
525         pw.increaseIndent();
526         dumpCallsInCollection(pw, mHoldingCalls);
527         pw.decreaseIndent();
528 
529         pw.println("Foreground call:");
530         pw.println(mForegroundCall);
531 
532         pw.println("CallAudioModeStateMachine pending messages:");
533         pw.increaseIndent();
534         mCallAudioModeStateMachine.dumpPendingMessages(pw);
535         pw.decreaseIndent();
536 
537         pw.println("CallAudioRouteStateMachine pending messages:");
538         pw.increaseIndent();
539         mCallAudioRouteStateMachine.dumpPendingMessages(pw);
540         pw.decreaseIndent();
541 
542         pw.println("BluetoothDeviceManager:");
543         pw.increaseIndent();
544         if (mBluetoothStateReceiver.getBluetoothDeviceManager() != null) {
545             mBluetoothStateReceiver.getBluetoothDeviceManager().dump(pw);
546         }
547         pw.decreaseIndent();
548     }
549 
550     @VisibleForTesting
setIsTonePlaying(boolean isTonePlaying)551     public void setIsTonePlaying(boolean isTonePlaying) {
552         mIsTonePlaying = isTonePlaying;
553         mCallAudioModeStateMachine.sendMessageWithArgs(
554                 isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING
555                         : CallAudioModeStateMachine.TONE_STOPPED_PLAYING,
556                 makeArgsForModeStateMachine());
557 
558         if (!isTonePlaying && mIsDisconnectedTonePlaying) {
559             mCallsManager.onDisconnectedTonePlaying(false);
560             mIsDisconnectedTonePlaying = false;
561         }
562     }
563 
onCallLeavingState(Call call, int state)564     private void onCallLeavingState(Call call, int state) {
565         switch (state) {
566             case CallState.ACTIVE:
567             case CallState.CONNECTING:
568                 onCallLeavingActiveDialingOrConnecting();
569                 break;
570             case CallState.RINGING:
571             case CallState.SIMULATED_RINGING:
572             case CallState.ANSWERED:
573                 onCallLeavingRinging();
574                 break;
575             case CallState.ON_HOLD:
576                 onCallLeavingHold();
577                 break;
578             case CallState.PULLING:
579                 onCallLeavingActiveDialingOrConnecting();
580                 break;
581             case CallState.DIALING:
582                 stopRingbackForCall(call);
583                 onCallLeavingActiveDialingOrConnecting();
584                 break;
585             case CallState.AUDIO_PROCESSING:
586                 onCallLeavingAudioProcessing();
587                 break;
588         }
589     }
590 
onCallEnteringState(Call call, int state)591     private void onCallEnteringState(Call call, int state) {
592         switch (state) {
593             case CallState.ACTIVE:
594             case CallState.CONNECTING:
595                 onCallEnteringActiveDialingOrConnecting();
596                 break;
597             case CallState.RINGING:
598             case CallState.SIMULATED_RINGING:
599                 onCallEnteringRinging();
600                 break;
601             case CallState.ON_HOLD:
602                 onCallEnteringHold();
603                 break;
604             case CallState.PULLING:
605                 onCallEnteringActiveDialingOrConnecting();
606                 break;
607             case CallState.DIALING:
608                 onCallEnteringActiveDialingOrConnecting();
609                 playRingbackForCall(call);
610                 break;
611             case CallState.ANSWERED:
612                 if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) {
613                     onCallEnteringActiveDialingOrConnecting();
614                 }
615                 break;
616             case CallState.AUDIO_PROCESSING:
617                 onCallEnteringAudioProcessing();
618                 break;
619         }
620     }
621 
onCallLeavingAudioProcessing()622     private void onCallLeavingAudioProcessing() {
623         if (mAudioProcessingCalls.size() == 0) {
624             mCallAudioModeStateMachine.sendMessageWithArgs(
625                     CallAudioModeStateMachine.NO_MORE_AUDIO_PROCESSING_CALLS,
626                     makeArgsForModeStateMachine());
627         }
628     }
629 
onCallEnteringAudioProcessing()630     private void onCallEnteringAudioProcessing() {
631         if (mAudioProcessingCalls.size() == 1) {
632             mCallAudioModeStateMachine.sendMessageWithArgs(
633                     CallAudioModeStateMachine.NEW_AUDIO_PROCESSING_CALL,
634                     makeArgsForModeStateMachine());
635         }
636     }
637 
onCallLeavingActiveDialingOrConnecting()638     private void onCallLeavingActiveDialingOrConnecting() {
639         if (mActiveDialingOrConnectingCalls.size() == 0) {
640             mCallAudioModeStateMachine.sendMessageWithArgs(
641                     CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS,
642                     makeArgsForModeStateMachine());
643         }
644     }
645 
onCallLeavingRinging()646     private void onCallLeavingRinging() {
647         if (mRingingCalls.size() == 0) {
648             mCallAudioModeStateMachine.sendMessageWithArgs(
649                     CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
650                     makeArgsForModeStateMachine());
651         }
652     }
653 
onCallLeavingHold()654     private void onCallLeavingHold() {
655         if (mHoldingCalls.size() == 0) {
656             mCallAudioModeStateMachine.sendMessageWithArgs(
657                     CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS,
658                     makeArgsForModeStateMachine());
659         }
660     }
661 
onCallEnteringActiveDialingOrConnecting()662     private void onCallEnteringActiveDialingOrConnecting() {
663         if (mActiveDialingOrConnectingCalls.size() == 1) {
664             mCallAudioModeStateMachine.sendMessageWithArgs(
665                     CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL,
666                     makeArgsForModeStateMachine());
667         }
668     }
669 
onCallEnteringRinging()670     private void onCallEnteringRinging() {
671         if (mRingingCalls.size() == 1) {
672             mCallAudioModeStateMachine.sendMessageWithArgs(
673                     CallAudioModeStateMachine.NEW_RINGING_CALL,
674                     makeArgsForModeStateMachine());
675         }
676     }
677 
onCallEnteringHold()678     private void onCallEnteringHold() {
679         if (mHoldingCalls.size() == 1) {
680             mCallAudioModeStateMachine.sendMessageWithArgs(
681                     CallAudioModeStateMachine.NEW_HOLDING_CALL,
682                     makeArgsForModeStateMachine());
683         }
684     }
685 
updateForegroundCall()686     private void updateForegroundCall() {
687         Call oldForegroundCall = mForegroundCall;
688         if (mActiveDialingOrConnectingCalls.size() > 0) {
689             // Give preference for connecting calls over active/dialing for foreground-ness.
690             Call possibleConnectingCall = null;
691             for (Call call : mActiveDialingOrConnectingCalls) {
692                 if (call.getState() == CallState.CONNECTING) {
693                     possibleConnectingCall = call;
694                 }
695             }
696             mForegroundCall = possibleConnectingCall == null ?
697                     mActiveDialingOrConnectingCalls.iterator().next() : possibleConnectingCall;
698         } else if (mRingingCalls.size() > 0) {
699             mForegroundCall = mRingingCalls.iterator().next();
700         } else if (mHoldingCalls.size() > 0) {
701             mForegroundCall = mHoldingCalls.iterator().next();
702         } else {
703             mForegroundCall = null;
704         }
705 
706         if (mForegroundCall != oldForegroundCall) {
707             mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
708                     CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
709             mDtmfLocalTonePlayer.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
710             maybePlayHoldTone();
711         }
712     }
713 
714     @NonNull
makeArgsForModeStateMachine()715     private CallAudioModeStateMachine.MessageArgs makeArgsForModeStateMachine() {
716         return new Builder()
717                 .setHasActiveOrDialingCalls(mActiveDialingOrConnectingCalls.size() > 0)
718                 .setHasRingingCalls(mRingingCalls.size() > 0)
719                 .setHasHoldingCalls(mHoldingCalls.size() > 0)
720                 .setHasAudioProcessingCalls(mAudioProcessingCalls.size() > 0)
721                 .setIsTonePlaying(mIsTonePlaying)
722                 .setForegroundCallIsVoip(
723                         mForegroundCall != null && mForegroundCall.getIsVoipAudioMode())
724                 .setSession(Log.createSubsession()).build();
725     }
726 
getBinForCall(Call call)727     private HashSet<Call> getBinForCall(Call call) {
728         if (call.getState() == CallState.ANSWERED) {
729             // If the call has the speed-up-mt-audio capability, treat answered state as active
730             // for audio purposes.
731             if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) {
732                 return mActiveDialingOrConnectingCalls;
733             }
734             return mRingingCalls;
735         }
736         return mCallStateToCalls.get(call.getState());
737     }
738 
removeCallFromAllBins(Call call)739     private void removeCallFromAllBins(Call call) {
740         for (int i = 0; i < mCallStateToCalls.size(); i++) {
741             mCallStateToCalls.valueAt(i).remove(call);
742         }
743     }
744 
playToneForDisconnectedCall(Call call)745     private void playToneForDisconnectedCall(Call call) {
746         // If this call is being disconnected as a result of being handed over to another call,
747         // we will not play a disconnect tone.
748         if (call.isHandoverInProgress()) {
749             Log.i(LOG_TAG, "Omitting tone because %s is being handed over.", call);
750             return;
751         }
752 
753         if (mForegroundCall != null && call != mForegroundCall && mCalls.size() > 1) {
754             Log.v(LOG_TAG, "Omitting tone because we are not foreground" +
755                     " and there is another call.");
756             return;
757         }
758 
759         if (call.getDisconnectCause() != null) {
760             int toneToPlay = InCallTonePlayer.TONE_INVALID;
761 
762             Log.v(this, "Disconnect cause: %s.", call.getDisconnectCause());
763 
764             switch(call.getDisconnectCause().getTone()) {
765                 case ToneGenerator.TONE_SUP_BUSY:
766                     toneToPlay = InCallTonePlayer.TONE_BUSY;
767                     break;
768                 case ToneGenerator.TONE_SUP_CONGESTION:
769                     toneToPlay = InCallTonePlayer.TONE_CONGESTION;
770                     break;
771                 case ToneGenerator.TONE_CDMA_REORDER:
772                     toneToPlay = InCallTonePlayer.TONE_REORDER;
773                     break;
774                 case ToneGenerator.TONE_CDMA_ABBR_INTERCEPT:
775                     toneToPlay = InCallTonePlayer.TONE_INTERCEPT;
776                     break;
777                 case ToneGenerator.TONE_CDMA_CALLDROP_LITE:
778                     toneToPlay = InCallTonePlayer.TONE_CDMA_DROP;
779                     break;
780                 case ToneGenerator.TONE_SUP_ERROR:
781                     toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER;
782                     break;
783                 case ToneGenerator.TONE_PROP_PROMPT:
784                     toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
785                     break;
786             }
787 
788             Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay);
789 
790             if (toneToPlay != InCallTonePlayer.TONE_INVALID) {
791                 boolean didToneStart = mPlayerFactory.createPlayer(toneToPlay).startTone();
792                 if (didToneStart) {
793                     mCallsManager.onDisconnectedTonePlaying(true);
794                     mIsDisconnectedTonePlaying = true;
795                 }
796             }
797         }
798     }
799 
playRingbackForCall(Call call)800     private void playRingbackForCall(Call call) {
801         if (call == mForegroundCall && call.isRingbackRequested()) {
802             mRingbackPlayer.startRingbackForCall(call);
803         }
804     }
805 
stopRingbackForCall(Call call)806     private void stopRingbackForCall(Call call) {
807         mRingbackPlayer.stopRingbackForCall(call);
808     }
809 
810     /**
811      * Determines if a hold tone should be played and then starts or stops it accordingly.
812      */
maybePlayHoldTone()813     private void maybePlayHoldTone() {
814         if (shouldPlayHoldTone()) {
815             if (mHoldTonePlayer == null) {
816                 mHoldTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
817                 mHoldTonePlayer.startTone();
818             }
819         } else {
820             if (mHoldTonePlayer != null) {
821                 mHoldTonePlayer.stopTone();
822                 mHoldTonePlayer = null;
823             }
824         }
825     }
826 
827     /**
828      * Determines if a hold tone should be played.
829      * A hold tone should be played only if foreground call is equals with call which is
830      * remotely held.
831      *
832      * @return {@code true} if the the hold tone should be played, {@code false} otherwise.
833      */
shouldPlayHoldTone()834     private boolean shouldPlayHoldTone() {
835         Call foregroundCall = getForegroundCall();
836         // If there is no foreground call, no hold tone should play.
837         if (foregroundCall == null) {
838             return false;
839         }
840 
841         // If another call is ringing, no hold tone should play.
842         if (mCallsManager.hasRingingCall()) {
843             return false;
844         }
845 
846         // If the foreground call isn't active, no hold tone should play. This might happen, for
847         // example, if the user puts a remotely held call on hold itself.
848         if (!foregroundCall.isActive()) {
849             return false;
850         }
851 
852         return foregroundCall.isRemotelyHeld();
853     }
854 
dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls)855     private void dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls) {
856         for (Call call : calls) {
857             if (call != null) pw.println(call.getId());
858         }
859     }
860 
maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(Call call)861     private void maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(Call call) {
862         // Check to see if the call being answered/rejected is the only ringing call, since this
863         // will be called before the connection service acknowledges the state change.
864         synchronized (mCallsManager.getLock()) {
865             if (mRingingCalls.size() == 0 ||
866                     (mRingingCalls.size() == 1 && call == mRingingCalls.iterator().next())) {
867                 mRinger.stopRinging();
868                 mRinger.stopCallWaiting();
869             }
870         }
871     }
872 
shouldPlayDisconnectTone(int oldState, int newState)873     private boolean shouldPlayDisconnectTone(int oldState, int newState) {
874         if (newState != CallState.DISCONNECTED) {
875             return false;
876         }
877         return oldState == CallState.ACTIVE ||
878                 oldState == CallState.DIALING ||
879                 oldState == CallState.ON_HOLD;
880     }
881 
882     @VisibleForTesting
getTrackedCalls()883     public Set<Call> getTrackedCalls() {
884         return mCalls;
885     }
886 
887     @VisibleForTesting
getCallStateToCalls()888     public SparseArray<LinkedHashSet<Call>> getCallStateToCalls() {
889         return mCallStateToCalls;
890     }
891 }
892