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