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.content.Context;
21 import android.media.IAudioService;
22 import android.media.ToneGenerator;
23 import android.os.Handler;
24 import android.os.HandlerThread;
25 import android.os.UserHandle;
26 import android.telecom.CallAudioState;
27 import android.telecom.Log;
28 import android.telecom.VideoProfile;
29 import android.util.SparseArray;
30 
31 import com.android.internal.annotations.VisibleForTesting;
32 import com.android.internal.util.IndentingPrintWriter;
33 import com.android.server.telecom.CallAudioModeStateMachine.MessageArgs.Builder;
34 import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
35 import com.android.server.telecom.flags.FeatureFlags;
36 
37 import java.util.Collection;
38 import java.util.HashSet;
39 import java.util.Set;
40 import java.util.LinkedHashSet;
41 import java.util.concurrent.CompletableFuture;
42 import java.util.stream.Collectors;
43 
44 
45 public class CallAudioManager extends CallsManagerListenerBase {
46 
47     public interface AudioServiceFactory {
getAudioService()48         IAudioService getAudioService();
49     }
50 
51     private final String LOG_TAG = CallAudioManager.class.getSimpleName();
52 
53     private final LinkedHashSet<Call> mActiveDialingOrConnectingCalls;
54     private final LinkedHashSet<Call> mRingingCalls;
55     private final LinkedHashSet<Call> mHoldingCalls;
56     private final LinkedHashSet<Call> mAudioProcessingCalls;
57     private final Set<Call> mCalls;
58     private final SparseArray<LinkedHashSet<Call>> mCallStateToCalls;
59 
60     private final CallAudioRouteAdapter mCallAudioRouteAdapter;
61     private final CallAudioModeStateMachine mCallAudioModeStateMachine;
62     private final BluetoothStateReceiver mBluetoothStateReceiver;
63     private final CallsManager mCallsManager;
64     private final InCallTonePlayer.Factory mPlayerFactory;
65     private final Ringer mRinger;
66     private final RingbackPlayer mRingbackPlayer;
67     private final DtmfLocalTonePlayer mDtmfLocalTonePlayer;
68     private final FeatureFlags mFeatureFlags;
69 
70     private Call mStreamingCall;
71     private Call mForegroundCall;
72     private CompletableFuture<Boolean> mCallRingingFuture;
73     private Thread mBtIcsBindingThread;
74     private boolean mIsTonePlaying = false;
75     private boolean mIsDisconnectedTonePlaying = false;
76     private InCallTonePlayer mHoldTonePlayer;
77     private final HandlerThread mHandlerThread;
78     private final Handler mHandler;
79 
CallAudioManager(CallAudioRouteAdapter callAudioRouteAdapter, CallsManager callsManager, CallAudioModeStateMachine callAudioModeStateMachine, InCallTonePlayer.Factory playerFactory, Ringer ringer, RingbackPlayer ringbackPlayer, BluetoothStateReceiver bluetoothStateReceiver, DtmfLocalTonePlayer dtmfLocalTonePlayer, FeatureFlags featureFlags)80     public CallAudioManager(CallAudioRouteAdapter callAudioRouteAdapter,
81             CallsManager callsManager,
82             CallAudioModeStateMachine callAudioModeStateMachine,
83             InCallTonePlayer.Factory playerFactory,
84             Ringer ringer,
85             RingbackPlayer ringbackPlayer,
86             BluetoothStateReceiver bluetoothStateReceiver,
87             DtmfLocalTonePlayer dtmfLocalTonePlayer,
88             FeatureFlags featureFlags) {
89         mActiveDialingOrConnectingCalls = new LinkedHashSet<>(1);
90         mRingingCalls = new LinkedHashSet<>(1);
91         mHoldingCalls = new LinkedHashSet<>(1);
92         mAudioProcessingCalls = new LinkedHashSet<>(1);
93         mStreamingCall = null;
94         mCalls = new HashSet<>();
95         mCallStateToCalls = new SparseArray<LinkedHashSet<Call>>() {{
96             put(CallState.CONNECTING, mActiveDialingOrConnectingCalls);
97             put(CallState.ACTIVE, mActiveDialingOrConnectingCalls);
98             put(CallState.DIALING, mActiveDialingOrConnectingCalls);
99             put(CallState.PULLING, mActiveDialingOrConnectingCalls);
100             put(CallState.RINGING, mRingingCalls);
101             put(CallState.ON_HOLD, mHoldingCalls);
102             put(CallState.SIMULATED_RINGING, mRingingCalls);
103             put(CallState.AUDIO_PROCESSING, mAudioProcessingCalls);
104         }};
105 
106         mCallAudioRouteAdapter = callAudioRouteAdapter;
107         mCallAudioModeStateMachine = callAudioModeStateMachine;
108         mCallsManager = callsManager;
109         mPlayerFactory = playerFactory;
110         mRinger = ringer;
111         mRingbackPlayer = ringbackPlayer;
112         mBluetoothStateReceiver = bluetoothStateReceiver;
113         mDtmfLocalTonePlayer = dtmfLocalTonePlayer;
114         mFeatureFlags = featureFlags;
115         mHandlerThread = new HandlerThread(this.getClass().getSimpleName());
116         mHandlerThread.start();
117         mHandler = new Handler(mHandlerThread.getLooper());
118 
119         mPlayerFactory.setCallAudioManager(this);
120         mCallAudioModeStateMachine.setCallAudioManager(this);
121         mCallAudioRouteAdapter.setCallAudioManager(this);
122     }
123 
124     @Override
onCallStateChanged(Call call, int oldState, int newState)125     public void onCallStateChanged(Call call, int oldState, int newState) {
126         if (shouldIgnoreCallForAudio(call)) {
127             // No audio management for calls in a conference, or external calls.
128             return;
129         }
130         if (oldState == newState) {
131             // State did not change, so no need to do anything.
132             return;
133         }
134         Log.i(this, "onCallStateChanged: Call state changed for TC@%s: %s -> %s", call.getId(),
135                 CallState.toString(oldState), CallState.toString(newState));
136 
137         removeCallFromAllBins(call);
138         HashSet<Call> newBinForCall = getBinForCall(call);
139         if (newBinForCall != null) {
140             newBinForCall.add(call);
141         }
142         sendCallStatusToBluetoothStateReceiver();
143 
144         updateForegroundCall();
145         if (shouldPlayDisconnectTone(oldState, newState)) {
146             playToneForDisconnectedCall(call);
147         }
148 
149         onCallLeavingState(call, oldState);
150         onCallEnteringState(call, newState);
151     }
152 
153     @Override
onCallAdded(Call call)154     public void onCallAdded(Call call) {
155         if (shouldIgnoreCallForAudio(call)) {
156             return; // Don't do audio handling for calls in a conference, or external calls.
157         }
158 
159         addCall(call);
160     }
161 
162     @Override
onCallRemoved(Call call)163     public void onCallRemoved(Call call) {
164         if (mStreamingCall == call) {
165             mStreamingCall = null;
166         }
167         if (shouldIgnoreCallForAudio(call)) {
168             return; // Don't do audio handling for calls in a conference, or external calls.
169         }
170 
171         removeCall(call);
172     }
173 
addCall(Call call)174     private void addCall(Call call) {
175         if (mCalls.contains(call)) {
176             Log.w(LOG_TAG, "Call TC@%s is being added twice.", call.getId());
177             return; // No guarantees that the same call won't get added twice.
178         }
179 
180         Log.d(LOG_TAG, "Call added with id TC@%s in state %s", call.getId(),
181                 CallState.toString(call.getState()));
182 
183         HashSet<Call> newBinForCall = getBinForCall(call);
184         if (newBinForCall != null) {
185             newBinForCall.add(call);
186         }
187         updateForegroundCall();
188         mCalls.add(call);
189         sendCallStatusToBluetoothStateReceiver();
190 
191         onCallEnteringState(call, call.getState());
192     }
193 
removeCall(Call call)194     private void removeCall(Call call) {
195         if (!mCalls.contains(call)) {
196             return; // No guarantees that the same call won't get removed twice.
197         }
198 
199         Log.d(LOG_TAG, "Call removed with id TC@%s in state %s", call.getId(),
200                 CallState.toString(call.getState()));
201 
202         removeCallFromAllBins(call);
203 
204         updateForegroundCall();
205         mCalls.remove(call);
206         sendCallStatusToBluetoothStateReceiver();
207 
208         onCallLeavingState(call, call.getState());
209     }
210 
sendCallStatusToBluetoothStateReceiver()211     private void sendCallStatusToBluetoothStateReceiver() {
212         // We're in a call if there are calls in mCalls that are not in mAudioProcessingCalls.
213         boolean isInCall = !mAudioProcessingCalls.containsAll(mCalls);
214         mBluetoothStateReceiver.setIsInCall(isInCall);
215     }
216 
217     /**
218      * Handles changes to the external state of a call.  External calls which become regular calls
219      * should be tracked, and regular calls which become external should no longer be tracked.
220      *
221      * @param call The call.
222      * @param isExternalCall {@code True} if the call is now external, {@code false} if it is now
223      *      a regular call.
224      */
225     @Override
onExternalCallChanged(Call call, boolean isExternalCall)226     public void onExternalCallChanged(Call call, boolean isExternalCall) {
227         if (isExternalCall) {
228             Log.d(LOG_TAG, "Removing call which became external ID %s", call.getId());
229             removeCall(call);
230         } else if (!isExternalCall) {
231             Log.d(LOG_TAG, "Adding external call which was pulled with ID %s", call.getId());
232             addCall(call);
233 
234             if (mCallsManager.isSpeakerphoneAutoEnabledForVideoCalls(call.getVideoState())) {
235                 // When pulling a video call, automatically enable the speakerphone.
236                 Log.d(LOG_TAG, "Switching to speaker because external video call %s was pulled." +
237                         call.getId());
238                 mCallAudioRouteAdapter.sendMessageWithSessionInfo(
239                         CallAudioRouteStateMachine.SWITCH_SPEAKER);
240             }
241         }
242     }
243 
244     /**
245      * Handles the changes to the streaming state of a call.
246      * @param call The call
247      * @param isStreaming {@code true} if the call is streaming, {@code false} otherwise
248      */
249     @Override
onCallStreamingStateChanged(Call call, boolean isStreaming)250     public void onCallStreamingStateChanged(Call call, boolean isStreaming) {
251         if (isStreaming) {
252             if (mStreamingCall == null) {
253                 mStreamingCall = call;
254                 mCallAudioModeStateMachine.sendMessageWithArgs(
255                         CallAudioModeStateMachine.START_CALL_STREAMING,
256                         makeArgsForModeStateMachine());
257             } else {
258                 Log.w(LOG_TAG, "Unexpected streaming call request for call %s while call "
259                         + "%s is streaming.", call.getId(), mStreamingCall.getId());
260             }
261         } else {
262             if (mStreamingCall == call) {
263                 mStreamingCall = null;
264                 mCallAudioModeStateMachine.sendMessageWithArgs(
265                         CallAudioModeStateMachine.STOP_CALL_STREAMING,
266                         makeArgsForModeStateMachine());
267             } else {
268                 Log.w(LOG_TAG, "Unexpected call streaming stop request for call %s while this call "
269                         + "is not streaming.", call.getId());
270             }
271         }
272     }
273 
274     /**
275      * Determines if {@link CallAudioManager} should do any audio routing operations for a call.
276      * We ignore child calls of a conference and external calls for audio routing purposes.
277      *
278      * @param call The call to check.
279      * @return {@code true} if the call should be ignored for audio routing, {@code false}
280      * otherwise
281      */
shouldIgnoreCallForAudio(Call call)282     private boolean shouldIgnoreCallForAudio(Call call) {
283         return call.getParentCall() != null || call.isExternalCall();
284     }
285 
286     @Override
onIncomingCallAnswered(Call call)287     public void onIncomingCallAnswered(Call call) {
288         if (!mCalls.contains(call)) {
289             return;
290         }
291 
292         // Turn off mute when a new incoming call is answered iff it's not a handover.
293         if (!call.isHandoverInProgress()) {
294             mute(false /* shouldMute */);
295         }
296 
297         maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call);
298     }
299 
300     @Override
onSessionModifyRequestReceived(Call call, VideoProfile videoProfile)301     public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) {
302         if (videoProfile == null) {
303             return;
304         }
305 
306         if (call != mForegroundCall) {
307             // We only play tones for foreground calls.
308             return;
309         }
310 
311         int previousVideoState = call.getVideoState();
312         int newVideoState = videoProfile.getVideoState();
313         Log.v(this, "onSessionModifyRequestReceived : videoProfile = " + VideoProfile
314                 .videoStateToString(newVideoState));
315 
316         boolean isUpgradeRequest = !VideoProfile.isReceptionEnabled(previousVideoState) &&
317                 VideoProfile.isReceptionEnabled(newVideoState);
318 
319         if (isUpgradeRequest) {
320             mPlayerFactory.createPlayer(call, InCallTonePlayer.TONE_VIDEO_UPGRADE).startTone();
321         }
322     }
323 
playRttUpgradeTone(Call call)324     public void playRttUpgradeTone(Call call) {
325         if (call != mForegroundCall) {
326             // We only play tones for foreground calls.
327             return;
328         }
329         mPlayerFactory.createPlayer(call, InCallTonePlayer.TONE_RTT_REQUEST).startTone();
330     }
331 
332     /**
333      * Play or stop a call hold tone for a call.  Triggered via
334      * {@link Connection#sendConnectionEvent(String)} when the
335      * {@link Connection#EVENT_ON_HOLD_TONE_START} event or
336      * {@link Connection#EVENT_ON_HOLD_TONE_STOP} event is passed through to the
337      *
338      * @param call The call which requested the hold tone.
339      */
340     @Override
onHoldToneRequested(Call call)341     public void onHoldToneRequested(Call call) {
342         maybePlayHoldTone(call);
343     }
344 
345     @Override
onIsVoipAudioModeChanged(Call call)346     public void onIsVoipAudioModeChanged(Call call) {
347         if (call != mForegroundCall) {
348             return;
349         }
350         mCallAudioModeStateMachine.sendMessageWithArgs(
351                 CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE,
352                 makeArgsForModeStateMachine());
353     }
354 
355     @Override
onRingbackRequested(Call call, boolean shouldRingback)356     public void onRingbackRequested(Call call, boolean shouldRingback) {
357         if (call == mForegroundCall && shouldRingback) {
358             mRingbackPlayer.startRingbackForCall(call);
359         } else {
360             mRingbackPlayer.stopRingbackForCall(call);
361         }
362     }
363 
364     @Override
onIncomingCallRejected(Call call, boolean rejectWithMessage, String message)365     public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String message) {
366         maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call);
367     }
368 
369     @Override
onIsConferencedChanged(Call call)370     public void onIsConferencedChanged(Call call) {
371         // This indicates a conferencing change, which shouldn't impact any audio mode stuff.
372         Call parentCall = call.getParentCall();
373         if (parentCall == null) {
374             // Indicates that the call should be tracked for audio purposes. Treat it as if it were
375             // just added.
376             Log.i(LOG_TAG, "Call TC@" + call.getId() + " left conference and will" +
377                             " now be tracked by CallAudioManager.");
378             onCallAdded(call);
379         } else {
380             // The call joined a conference, so stop tracking it.
381             removeCallFromAllBins(call);
382             updateForegroundCall();
383             mCalls.remove(call);
384         }
385     }
386 
387     @Override
onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs, ConnectionServiceWrapper newCs)388     public void onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs,
389             ConnectionServiceWrapper newCs) {
390         mCallAudioRouteAdapter.sendMessageWithSessionInfo(
391                 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
392     }
393 
394     @Override
onVideoStateChanged(Call call, int previousVideoState, int newVideoState)395     public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {
396         if (call != getForegroundCall()) {
397             Log.d(LOG_TAG, "Ignoring video state change from %s to %s for call %s -- not " +
398                     "foreground.", VideoProfile.videoStateToString(previousVideoState),
399                     VideoProfile.videoStateToString(newVideoState), call.getId());
400             return;
401         }
402 
403         if (!VideoProfile.isVideo(previousVideoState) &&
404                 mCallsManager.isSpeakerphoneAutoEnabledForVideoCalls(newVideoState)) {
405             Log.d(LOG_TAG, "Switching to speaker because call %s transitioned video state from %s" +
406                     " to %s", call.getId(), VideoProfile.videoStateToString(previousVideoState),
407                     VideoProfile.videoStateToString(newVideoState));
408             mCallAudioRouteAdapter.sendMessageWithSessionInfo(
409                     CallAudioRouteStateMachine.SWITCH_SPEAKER);
410         }
411     }
412 
getCallAudioState()413     public CallAudioState getCallAudioState() {
414         return mCallAudioRouteAdapter.getCurrentCallAudioState();
415     }
416 
getPossiblyHeldForegroundCall()417     public Call getPossiblyHeldForegroundCall() {
418         return mForegroundCall;
419     }
420 
getForegroundCall()421     public Call getForegroundCall() {
422         if (mForegroundCall != null && mForegroundCall.getState() != CallState.ON_HOLD) {
423             return mForegroundCall;
424         }
425         return null;
426     }
427 
428     @VisibleForTesting
toggleMute()429     public void toggleMute() {
430         // Don't mute if there are any emergency calls.
431         if (mCallsManager.isInEmergencyCall()) {
432             Log.v(this, "ignoring toggleMute for emergency call");
433             return;
434         }
435         mCallAudioRouteAdapter.sendMessageWithSessionInfo(
436                 CallAudioRouteStateMachine.TOGGLE_MUTE);
437     }
438 
439     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
onRingerModeChange()440     public void onRingerModeChange() {
441         mCallAudioModeStateMachine.sendMessageWithArgs(
442                 CallAudioModeStateMachine.RINGER_MODE_CHANGE, makeArgsForModeStateMachine());
443     }
444 
445     @VisibleForTesting
mute(boolean shouldMute)446     public void mute(boolean shouldMute) {
447         Log.v(this, "mute, shouldMute: %b", shouldMute);
448 
449         // Don't mute if there are any emergency calls.
450         if (mCallsManager.isInEmergencyCall()) {
451             shouldMute = false;
452             Log.v(this, "ignoring mute for emergency call");
453         }
454 
455         mCallAudioRouteAdapter.sendMessageWithSessionInfo(shouldMute
456                 ? CallAudioRouteStateMachine.MUTE_ON : CallAudioRouteStateMachine.MUTE_OFF);
457     }
458 
459     /**
460      * Changed the audio route, for example from earpiece to speaker phone.
461      *
462      * @param route The new audio route to use. See {@link CallAudioState}.
463      * @param bluetoothAddress the address of the desired bluetooth device, if route is
464      * {@link CallAudioState#ROUTE_BLUETOOTH}.
465      */
466     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
setAudioRoute(int route, String bluetoothAddress)467     public void setAudioRoute(int route, String bluetoothAddress) {
468         Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route));
469         switch (route) {
470             case CallAudioState.ROUTE_BLUETOOTH:
471                 mCallAudioRouteAdapter.sendMessageWithSessionInfo(
472                         CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH, 0, bluetoothAddress);
473                 return;
474             case CallAudioState.ROUTE_SPEAKER:
475                 mCallAudioRouteAdapter.sendMessageWithSessionInfo(
476                         CallAudioRouteStateMachine.USER_SWITCH_SPEAKER);
477                 return;
478             case CallAudioState.ROUTE_WIRED_HEADSET:
479                 mCallAudioRouteAdapter.sendMessageWithSessionInfo(
480                         CallAudioRouteStateMachine.USER_SWITCH_HEADSET);
481                 return;
482             case CallAudioState.ROUTE_EARPIECE:
483                 mCallAudioRouteAdapter.sendMessageWithSessionInfo(
484                         CallAudioRouteStateMachine.USER_SWITCH_EARPIECE);
485                 return;
486             case CallAudioState.ROUTE_WIRED_OR_EARPIECE:
487                 mCallAudioRouteAdapter.sendMessageWithSessionInfo(
488                         CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE,
489                         CallAudioRouteStateMachine.NO_INCLUDE_BLUETOOTH_IN_BASELINE);
490                 return;
491             default:
492                 Log.w(this, "InCallService requested an invalid audio route: %d", route);
493         }
494     }
495 
496     /**
497      * Switch call audio routing to the baseline route, including bluetooth headsets if there are
498      * any connected.
499      */
switchBaseline()500     void switchBaseline() {
501         Log.i(this, "switchBaseline");
502         mCallAudioRouteAdapter.sendMessageWithSessionInfo(
503                 CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE,
504                 CallAudioRouteStateMachine.INCLUDE_BLUETOOTH_IN_BASELINE);
505     }
506 
silenceRingers(Context context, UserHandle callingUser, boolean hasCrossUserPermission)507     Set<UserHandle> silenceRingers(Context context, UserHandle callingUser,
508             boolean hasCrossUserPermission) {
509         // Store all users from calls that were silenced so that we can silence the
510         // InCallServices which are associated with those users.
511         Set<UserHandle> userHandles = new HashSet<>();
512         boolean allCallSilenced = true;
513         synchronized (mCallsManager.getLock()) {
514             for (Call call : mRingingCalls) {
515                 UserHandle userFromCall = call.getAssociatedUser();
516                 // Do not try to silence calls when calling user is different from the phone account
517                 // user, the account does not have CAPABILITY_MULTI_USER enabled, or if the user
518                 // does not have the INTERACT_ACROSS_USERS permission enabled.
519                 if (!hasCrossUserPermission && !mCallsManager
520                         .isCallVisibleForUser(call, callingUser)) {
521                     allCallSilenced = false;
522                     continue;
523                 }
524                 userHandles.add(userFromCall);
525                 call.silence();
526             }
527 
528             // If all the calls were silenced, we can stop the ringer.
529             if (allCallSilenced) {
530                 mRinger.stopRinging();
531                 mRinger.stopCallWaiting();
532             }
533         }
534         return userHandles;
535     }
536 
isRingtonePlaying()537     public boolean isRingtonePlaying() {
538         return mRinger.isRinging();
539     }
540 
541     @VisibleForTesting
startRinging()542     public boolean startRinging() {
543         synchronized (mCallsManager.getLock()) {
544             Call localForegroundCall = mForegroundCall;
545             boolean result = mRinger.startRinging(localForegroundCall,
546                     mCallAudioRouteAdapter.isHfpDeviceAvailable());
547             if (result) {
548                 localForegroundCall.setStartRingTime();
549             }
550             return result;
551         }
552     }
553 
554     @VisibleForTesting
startCallWaiting(String reason)555     public void startCallWaiting(String reason) {
556         synchronized (mCallsManager.getLock()) {
557             if (mRingingCalls.size() == 1) {
558                 mRinger.startCallWaiting(mRingingCalls.iterator().next(), reason);
559             }
560         }
561     }
562 
563     @VisibleForTesting
stopRinging()564     public void stopRinging() {
565         synchronized (mCallsManager.getLock()) {
566             mRinger.stopRinging();
567         }
568     }
569 
570     @VisibleForTesting
stopCallWaiting()571     public void stopCallWaiting() {
572         synchronized (mCallsManager.getLock()) {
573             mRinger.stopCallWaiting();
574         }
575     }
576 
577     @VisibleForTesting
setCallAudioRouteFocusState(int focusState)578     public void setCallAudioRouteFocusState(int focusState) {
579         mCallAudioRouteAdapter.sendMessageWithSessionInfo(
580                 CallAudioRouteStateMachine.SWITCH_FOCUS, focusState);
581     }
582 
notifyAudioOperationsComplete()583     public void notifyAudioOperationsComplete() {
584         mCallAudioModeStateMachine.sendMessageWithArgs(
585                 CallAudioModeStateMachine.AUDIO_OPERATIONS_COMPLETE, makeArgsForModeStateMachine());
586     }
587 
588     @VisibleForTesting
getCallAudioRouteAdapter()589     public CallAudioRouteAdapter getCallAudioRouteAdapter() {
590         return mCallAudioRouteAdapter;
591     }
592 
593     @VisibleForTesting
getCallAudioModeStateMachine()594     public CallAudioModeStateMachine getCallAudioModeStateMachine() {
595         return mCallAudioModeStateMachine;
596     }
597 
dump(IndentingPrintWriter pw)598     void dump(IndentingPrintWriter pw) {
599         pw.println("All calls:");
600         pw.increaseIndent();
601         dumpCallsInCollection(pw, mCalls);
602         pw.decreaseIndent();
603 
604         pw.println("Active dialing, or connecting calls:");
605         pw.increaseIndent();
606         dumpCallsInCollection(pw, mActiveDialingOrConnectingCalls);
607         pw.decreaseIndent();
608 
609         pw.println("Ringing calls:");
610         pw.increaseIndent();
611         dumpCallsInCollection(pw, mRingingCalls);
612         pw.decreaseIndent();
613 
614         pw.println("Holding calls:");
615         pw.increaseIndent();
616         dumpCallsInCollection(pw, mHoldingCalls);
617         pw.decreaseIndent();
618 
619         pw.println("Foreground call:");
620         pw.println(mForegroundCall);
621 
622         pw.println("CallAudioModeStateMachine:");
623         pw.increaseIndent();
624         mCallAudioModeStateMachine.dump(pw);
625         pw.decreaseIndent();
626 
627         pw.println("mCallAudioRouteAdapter:");
628         pw.increaseIndent();
629         mCallAudioRouteAdapter.dump(pw);
630         pw.decreaseIndent();
631 
632         pw.println("BluetoothDeviceManager:");
633         pw.increaseIndent();
634         if (mBluetoothStateReceiver.getBluetoothDeviceManager() != null) {
635             mBluetoothStateReceiver.getBluetoothDeviceManager().dump(pw);
636         }
637         pw.decreaseIndent();
638     }
639 
640     @VisibleForTesting
setIsTonePlaying(Call call, boolean isTonePlaying)641     public void setIsTonePlaying(Call call, boolean isTonePlaying) {
642         Log.i(this, "setIsTonePlaying; isTonePlaying=%b", isTonePlaying);
643         mIsTonePlaying = isTonePlaying;
644         mCallAudioModeStateMachine.sendMessageWithArgs(
645                 isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING
646                         : CallAudioModeStateMachine.TONE_STOPPED_PLAYING,
647                 makeArgsForModeStateMachine());
648 
649         if (!isTonePlaying && mIsDisconnectedTonePlaying) {
650             mCallsManager.onDisconnectedTonePlaying(call, false);
651             mIsDisconnectedTonePlaying = false;
652         }
653     }
654 
onCallLeavingState(Call call, int state)655     private void onCallLeavingState(Call call, int state) {
656         switch (state) {
657             case CallState.ACTIVE:
658             case CallState.CONNECTING:
659                 onCallLeavingActiveDialingOrConnecting();
660                 break;
661             case CallState.RINGING:
662             case CallState.SIMULATED_RINGING:
663             case CallState.ANSWERED:
664                 onCallLeavingRinging();
665                 break;
666             case CallState.ON_HOLD:
667                 onCallLeavingHold();
668                 break;
669             case CallState.PULLING:
670                 onCallLeavingActiveDialingOrConnecting();
671                 break;
672             case CallState.DIALING:
673                 stopRingbackForCall(call);
674                 onCallLeavingActiveDialingOrConnecting();
675                 break;
676             case CallState.AUDIO_PROCESSING:
677                 onCallLeavingAudioProcessing();
678                 break;
679         }
680     }
681 
onCallEnteringState(Call call, int state)682     private void onCallEnteringState(Call call, int state) {
683         switch (state) {
684             case CallState.ACTIVE:
685             case CallState.CONNECTING:
686                 onCallEnteringActiveDialingOrConnecting();
687                 break;
688             case CallState.RINGING:
689             case CallState.SIMULATED_RINGING:
690                 onCallEnteringRinging();
691                 break;
692             case CallState.ON_HOLD:
693                 onCallEnteringHold();
694                 break;
695             case CallState.PULLING:
696                 onCallEnteringActiveDialingOrConnecting();
697                 break;
698             case CallState.DIALING:
699                 onCallEnteringActiveDialingOrConnecting();
700                 playRingbackForCall(call);
701                 break;
702             case CallState.ANSWERED:
703                 if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) {
704                     onCallEnteringActiveDialingOrConnecting();
705                 }
706                 break;
707             case CallState.AUDIO_PROCESSING:
708                 onCallEnteringAudioProcessing();
709                 break;
710         }
711     }
712 
onCallLeavingAudioProcessing()713     private void onCallLeavingAudioProcessing() {
714         if (mAudioProcessingCalls.size() == 0) {
715             mCallAudioModeStateMachine.sendMessageWithArgs(
716                     CallAudioModeStateMachine.NO_MORE_AUDIO_PROCESSING_CALLS,
717                     makeArgsForModeStateMachine());
718         }
719     }
720 
onCallEnteringAudioProcessing()721     private void onCallEnteringAudioProcessing() {
722         if (mAudioProcessingCalls.size() == 1) {
723             mCallAudioModeStateMachine.sendMessageWithArgs(
724                     CallAudioModeStateMachine.NEW_AUDIO_PROCESSING_CALL,
725                     makeArgsForModeStateMachine());
726         }
727     }
728 
onCallLeavingActiveDialingOrConnecting()729     private void onCallLeavingActiveDialingOrConnecting() {
730         if (mActiveDialingOrConnectingCalls.size() == 0) {
731             mCallAudioModeStateMachine.sendMessageWithArgs(
732                     CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS,
733                     makeArgsForModeStateMachine());
734         }
735     }
736 
onCallLeavingRinging()737     private void onCallLeavingRinging() {
738         if (mRingingCalls.size() == 0) {
739             mCallAudioModeStateMachine.sendMessageWithArgs(
740                     CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
741                     makeArgsForModeStateMachine());
742         }
743     }
744 
onCallLeavingHold()745     private void onCallLeavingHold() {
746         if (mHoldingCalls.size() == 0) {
747             mCallAudioModeStateMachine.sendMessageWithArgs(
748                     CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS,
749                     makeArgsForModeStateMachine());
750         }
751     }
752 
onCallEnteringActiveDialingOrConnecting()753     private void onCallEnteringActiveDialingOrConnecting() {
754         if (mActiveDialingOrConnectingCalls.size() == 1) {
755             mCallAudioModeStateMachine.sendMessageWithArgs(
756                     CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL,
757                     makeArgsForModeStateMachine());
758         }
759     }
760 
onCallEnteringRinging()761     private void onCallEnteringRinging() {
762         if (mRingingCalls.size() == 1) {
763             Log.i(this, "onCallEnteringRinging: mFeatureFlags.separatelyBindToBtIncallService() ? %s",
764                     mFeatureFlags.separatelyBindToBtIncallService());
765             Log.i(this, "onCallEnteringRinging: mRingingCalls.getFirst().getBtIcsFuture() = %s",
766                     mRingingCalls.getFirst().getBtIcsFuture());
767             if (mFeatureFlags.separatelyBindToBtIncallService()
768                     && mRingingCalls.getFirst().getBtIcsFuture() != null) {
769                 mCallRingingFuture  = mRingingCalls.getFirst().getBtIcsFuture()
770                         .thenComposeAsync((completed) -> {
771                             mCallAudioModeStateMachine.sendMessageWithArgs(
772                                     CallAudioModeStateMachine.NEW_RINGING_CALL,
773                                     makeArgsForModeStateMachine());
774                             return CompletableFuture.completedFuture(completed);
775                         }, new LoggedHandlerExecutor(mHandler, "CAM.oCER", mCallsManager.getLock()))
776                         .exceptionally((throwable) -> {
777                             Log.e(this, throwable, "Error while executing BT ICS future");
778                             // Fallback on performing computation on a separate thread.
779                             handleBtBindingWaitFallback();
780                             return null;
781                         });
782             } else {
783                 mCallAudioModeStateMachine.sendMessageWithArgs(
784                         CallAudioModeStateMachine.NEW_RINGING_CALL,
785                         makeArgsForModeStateMachine());
786             }
787         }
788     }
789 
handleBtBindingWaitFallback()790     private void handleBtBindingWaitFallback() {
791         // Wait until the BT ICS binding completed to request further audio route change
792         mBtIcsBindingThread = new Thread(() -> {
793             mRingingCalls.getFirst().waitForBtIcs();
794             mCallAudioModeStateMachine.sendMessageWithArgs(
795                     CallAudioModeStateMachine.NEW_RINGING_CALL,
796                     makeArgsForModeStateMachine());
797         });
798         mBtIcsBindingThread.start();
799     }
800 
onCallEnteringHold()801     private void onCallEnteringHold() {
802         if (mHoldingCalls.size() == 1) {
803             mCallAudioModeStateMachine.sendMessageWithArgs(
804                     CallAudioModeStateMachine.NEW_HOLDING_CALL,
805                     makeArgsForModeStateMachine());
806         }
807     }
808 
updateForegroundCall()809     private void updateForegroundCall() {
810         Call oldForegroundCall = mForegroundCall;
811 
812         if (mActiveDialingOrConnectingCalls.size() > 0) {
813             // Give preference for connecting calls over active/dialing for foreground-ness.
814             Call possibleConnectingCall = null;
815             for (Call call : mActiveDialingOrConnectingCalls) {
816                 if (call.getState() == CallState.CONNECTING) {
817                     possibleConnectingCall = call;
818                 }
819             }
820             if (mFeatureFlags.ensureAudioModeUpdatesOnForegroundCallChange()) {
821                 // Prefer a connecting call
822                 if (possibleConnectingCall != null) {
823                     mForegroundCall = possibleConnectingCall;
824                 } else {
825                     // Next, prefer an active or dialing call which is not in the process of being
826                     // disconnected.
827                     mForegroundCall = mActiveDialingOrConnectingCalls
828                             .stream()
829                             .filter(c -> (c.getState() == CallState.ACTIVE
830                                     || c.getState() == CallState.DIALING)
831                                     && !c.isLocallyDisconnecting())
832                             .findFirst()
833                             // If we can't find one, then just fall back to the first one.
834                             .orElse(mActiveDialingOrConnectingCalls.iterator().next());
835                 }
836             } else {
837                 // Legacy (buggy) behavior.
838                 mForegroundCall = possibleConnectingCall == null ?
839                         mActiveDialingOrConnectingCalls.iterator().next() : possibleConnectingCall;
840             }
841         } else if (mRingingCalls.size() > 0) {
842             mForegroundCall = mRingingCalls.iterator().next();
843         } else if (mHoldingCalls.size() > 0) {
844             mForegroundCall = mHoldingCalls.iterator().next();
845         } else {
846             mForegroundCall = null;
847         }
848         Log.i(this, "updateForegroundCall; oldFg=%s, newFg=%s, aDC=%s, ring=%s, hold=%s",
849                 (oldForegroundCall == null ? "none" : oldForegroundCall.getId()),
850                 (mForegroundCall == null ? "none" : mForegroundCall.getId()),
851                 mActiveDialingOrConnectingCalls.stream().map(c -> c.getId()).collect(
852                         Collectors.joining(",")),
853                 mRingingCalls.stream().map(c -> c.getId()).collect(Collectors.joining(",")),
854                 mHoldingCalls.stream().map(c -> c.getId()).collect(Collectors.joining(","))
855         );
856         if (mForegroundCall != oldForegroundCall) {
857             mCallAudioRouteAdapter.sendMessageWithSessionInfo(
858                     CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
859 
860             if (mForegroundCall != null
861                     && mFeatureFlags.ensureAudioModeUpdatesOnForegroundCallChange()) {
862                 // Ensure the voip audio mode for the new foreground call is taken into account.
863                 mCallAudioModeStateMachine.sendMessageWithArgs(
864                         CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE,
865                         makeArgsForModeStateMachine());
866             }
867             mDtmfLocalTonePlayer.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
868             maybePlayHoldTone(oldForegroundCall);
869         }
870     }
871 
872     @NonNull
makeArgsForModeStateMachine()873     private CallAudioModeStateMachine.MessageArgs makeArgsForModeStateMachine() {
874         return new Builder()
875                 .setHasActiveOrDialingCalls(mActiveDialingOrConnectingCalls.size() > 0)
876                 .setHasRingingCalls(mRingingCalls.size() > 0)
877                 .setHasHoldingCalls(mHoldingCalls.size() > 0)
878                 .setHasAudioProcessingCalls(mAudioProcessingCalls.size() > 0)
879                 .setIsTonePlaying(mIsTonePlaying)
880                 .setIsStreaming((mStreamingCall != null) && (!mStreamingCall.isDisconnected()))
881                 .setForegroundCallIsVoip(
882                         mForegroundCall != null && isCallVoip(mForegroundCall))
883                 .setSession(Log.createSubsession()).build();
884     }
885 
886     /**
887      * Determines if a {@link Call} is a VOIP call for audio purposes.
888      * For top level calls, we get this from {@link Call#getIsVoipAudioMode()}.  A {@link Call}
889      * representing a {@link android.telecom.Conference}, however, has no means of specifying that
890      * it is a VOIP conference, so we will get that attribute from one of the children.
891      * @param call The call.
892      * @return {@code true} if the call is a VOIP call, {@code false} if is a SIM call.
893      */
894     @VisibleForTesting
isCallVoip(Call call)895     public boolean isCallVoip(Call call) {
896         if (call.isConference() && call.getChildCalls() != null
897                 && call.getChildCalls().size() > 0 ) {
898             // If this is a conference with children, we can get the VOIP audio mode attribute from
899             // one of the children.  The Conference doesn't have a VOIP audio mode property, so we
900             // need to infer from the first child.
901             Call firstChild = call.getChildCalls().get(0);
902             return firstChild.getIsVoipAudioMode();
903         }
904         return call.getIsVoipAudioMode();
905     }
906 
getBinForCall(Call call)907     private HashSet<Call> getBinForCall(Call call) {
908         if (call.getState() == CallState.ANSWERED) {
909             // If the call has the speed-up-mt-audio capability, treat answered state as active
910             // for audio purposes.
911             if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) {
912                 return mActiveDialingOrConnectingCalls;
913             }
914             return mRingingCalls;
915         }
916         return mCallStateToCalls.get(call.getState());
917     }
918 
removeCallFromAllBins(Call call)919     private void removeCallFromAllBins(Call call) {
920         for (int i = 0; i < mCallStateToCalls.size(); i++) {
921             mCallStateToCalls.valueAt(i).remove(call);
922         }
923     }
924 
playToneForDisconnectedCall(Call call)925     private void playToneForDisconnectedCall(Call call) {
926         // If this call is being disconnected as a result of being handed over to another call,
927         // we will not play a disconnect tone.
928         if (call.isHandoverInProgress()) {
929             Log.i(LOG_TAG, "Omitting tone because %s is being handed over.", call);
930             completeDisconnectToneFuture(call);
931             return;
932         }
933 
934         if (mForegroundCall != null && call != mForegroundCall && mCalls.size() > 1) {
935             Log.v(LOG_TAG, "Omitting tone because we are not foreground" +
936                     " and there is another call.");
937             completeDisconnectToneFuture(call);
938             return;
939         }
940 
941         if (call.getDisconnectCause() != null) {
942             int toneToPlay = InCallTonePlayer.TONE_INVALID;
943 
944             Log.v(this, "Disconnect cause: %s.", call.getDisconnectCause());
945 
946             switch(call.getDisconnectCause().getTone()) {
947                 case ToneGenerator.TONE_SUP_BUSY:
948                     toneToPlay = InCallTonePlayer.TONE_BUSY;
949                     break;
950                 case ToneGenerator.TONE_SUP_CONGESTION:
951                     toneToPlay = InCallTonePlayer.TONE_CONGESTION;
952                     break;
953                 case ToneGenerator.TONE_CDMA_REORDER:
954                     toneToPlay = InCallTonePlayer.TONE_REORDER;
955                     break;
956                 case ToneGenerator.TONE_CDMA_ABBR_INTERCEPT:
957                     toneToPlay = InCallTonePlayer.TONE_INTERCEPT;
958                     break;
959                 case ToneGenerator.TONE_CDMA_CALLDROP_LITE:
960                     toneToPlay = InCallTonePlayer.TONE_CDMA_DROP;
961                     break;
962                 case ToneGenerator.TONE_SUP_ERROR:
963                     toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER;
964                     break;
965                 case ToneGenerator.TONE_PROP_PROMPT:
966                     toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
967                     break;
968             }
969 
970             Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay);
971 
972             if (toneToPlay != InCallTonePlayer.TONE_INVALID) {
973                 boolean didToneStart = mPlayerFactory.createPlayer(call, toneToPlay).startTone();
974                 if (didToneStart) {
975                     mCallsManager.onDisconnectedTonePlaying(call, true);
976                     mIsDisconnectedTonePlaying = true;
977                 }
978             } else {
979                 completeDisconnectToneFuture(call);
980             }
981         }
982     }
983 
playRingbackForCall(Call call)984     private void playRingbackForCall(Call call) {
985         if (call == mForegroundCall && call.isRingbackRequested()) {
986             mRingbackPlayer.startRingbackForCall(call);
987         }
988     }
989 
stopRingbackForCall(Call call)990     private void stopRingbackForCall(Call call) {
991         mRingbackPlayer.stopRingbackForCall(call);
992     }
993 
994     /**
995      * Determines if a hold tone should be played and then starts or stops it accordingly.
996      */
maybePlayHoldTone(Call call)997     private void maybePlayHoldTone(Call call) {
998         if (shouldPlayHoldTone()) {
999             if (mHoldTonePlayer == null) {
1000                 mHoldTonePlayer = mPlayerFactory.createPlayer(call,
1001                         InCallTonePlayer.TONE_CALL_WAITING);
1002                 mHoldTonePlayer.startTone();
1003             }
1004         } else {
1005             if (mHoldTonePlayer != null) {
1006                 mHoldTonePlayer.stopTone();
1007                 mHoldTonePlayer = null;
1008             }
1009         }
1010     }
1011 
1012     /**
1013      * Determines if a hold tone should be played.
1014      * A hold tone should be played only if foreground call is equals with call which is
1015      * remotely held.
1016      *
1017      * @return {@code true} if the the hold tone should be played, {@code false} otherwise.
1018      */
shouldPlayHoldTone()1019     private boolean shouldPlayHoldTone() {
1020         Call foregroundCall = getForegroundCall();
1021         // If there is no foreground call, no hold tone should play.
1022         if (foregroundCall == null) {
1023             return false;
1024         }
1025 
1026         // If another call is ringing, no hold tone should play.
1027         if (mCallsManager.hasRingingCall()) {
1028             return false;
1029         }
1030 
1031         // If the foreground call isn't active, no hold tone should play. This might happen, for
1032         // example, if the user puts a remotely held call on hold itself.
1033         if (!foregroundCall.isActive()) {
1034             return false;
1035         }
1036 
1037         return foregroundCall.isRemotelyHeld();
1038     }
1039 
dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls)1040     private void dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls) {
1041         for (Call call : calls) {
1042             if (call != null) pw.println(call.getId());
1043         }
1044     }
1045 
maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(Call call)1046     private void maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(Call call) {
1047         // Check to see if the call being answered/rejected is the only ringing call, since this
1048         // will be called before the connection service acknowledges the state change.
1049         synchronized (mCallsManager.getLock()) {
1050             if (mRingingCalls.size() == 0 ||
1051                     (mRingingCalls.size() == 1 && call == mRingingCalls.iterator().next())) {
1052                 mRinger.stopRinging();
1053                 mRinger.stopCallWaiting();
1054             }
1055         }
1056     }
1057 
shouldPlayDisconnectTone(int oldState, int newState)1058     private boolean shouldPlayDisconnectTone(int oldState, int newState) {
1059         if (newState != CallState.DISCONNECTED) {
1060             return false;
1061         }
1062         return oldState == CallState.ACTIVE ||
1063                 oldState == CallState.DIALING ||
1064                 oldState == CallState.ON_HOLD;
1065     }
1066 
completeDisconnectToneFuture(Call call)1067     private void completeDisconnectToneFuture(Call call) {
1068         CompletableFuture<Void> disconnectedToneFuture = mCallsManager.getInCallController()
1069                 .getDisconnectedToneBtFutures().get(call.getId());
1070         if (disconnectedToneFuture != null) {
1071             disconnectedToneFuture.complete(null);
1072         }
1073     }
1074 
1075     @VisibleForTesting
getTrackedCalls()1076     public Set<Call> getTrackedCalls() {
1077         return mCalls;
1078     }
1079 
1080     @VisibleForTesting
getCallStateToCalls()1081     public SparseArray<LinkedHashSet<Call>> getCallStateToCalls() {
1082         return mCallStateToCalls;
1083     }
1084 
1085     @VisibleForTesting
getCallRingingFuture()1086     public CompletableFuture<Boolean> getCallRingingFuture() {
1087         return mCallRingingFuture;
1088     }
1089 }
1090