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