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