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