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