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