Home | History | Annotate | Download | only in telecom
      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 
     30 import java.util.Collection;
     31 import java.util.HashSet;
     32 import java.util.Set;
     33 import java.util.LinkedHashSet;
     34 
     35 public class CallAudioManager extends CallsManagerListenerBase {
     36 
     37     public interface AudioServiceFactory {
     38         IAudioService getAudioService();
     39     }
     40 
     41     private final String LOG_TAG = CallAudioManager.class.getSimpleName();
     42 
     43     private final LinkedHashSet<Call> mActiveDialingOrConnectingCalls;
     44     private final LinkedHashSet<Call> mRingingCalls;
     45     private final LinkedHashSet<Call> mHoldingCalls;
     46     private final Set<Call> mCalls;
     47     private final SparseArray<LinkedHashSet<Call>> mCallStateToCalls;
     48 
     49     private final CallAudioRouteStateMachine mCallAudioRouteStateMachine;
     50     private final CallAudioModeStateMachine mCallAudioModeStateMachine;
     51     private final CallsManager mCallsManager;
     52     private final InCallTonePlayer.Factory mPlayerFactory;
     53     private final Ringer mRinger;
     54     private final RingbackPlayer mRingbackPlayer;
     55     private final DtmfLocalTonePlayer mDtmfLocalTonePlayer;
     56 
     57     private Call mForegroundCall;
     58     private boolean mIsTonePlaying = false;
     59     private InCallTonePlayer mHoldTonePlayer;
     60 
     61     public CallAudioManager(CallAudioRouteStateMachine callAudioRouteStateMachine,
     62             CallsManager callsManager,
     63             CallAudioModeStateMachine callAudioModeStateMachine,
     64             InCallTonePlayer.Factory playerFactory,
     65             Ringer ringer,
     66             RingbackPlayer ringbackPlayer,
     67             DtmfLocalTonePlayer dtmfLocalTonePlayer) {
     68         mActiveDialingOrConnectingCalls = new LinkedHashSet<>();
     69         mRingingCalls = new LinkedHashSet<>();
     70         mHoldingCalls = new LinkedHashSet<>();
     71         mCalls = new HashSet<>();
     72         mCallStateToCalls = new SparseArray<LinkedHashSet<Call>>() {{
     73             put(CallState.CONNECTING, mActiveDialingOrConnectingCalls);
     74             put(CallState.ACTIVE, mActiveDialingOrConnectingCalls);
     75             put(CallState.DIALING, mActiveDialingOrConnectingCalls);
     76             put(CallState.PULLING, mActiveDialingOrConnectingCalls);
     77             put(CallState.RINGING, mRingingCalls);
     78             put(CallState.ON_HOLD, mHoldingCalls);
     79         }};
     80 
     81         mCallAudioRouteStateMachine = callAudioRouteStateMachine;
     82         mCallAudioModeStateMachine = callAudioModeStateMachine;
     83         mCallsManager = callsManager;
     84         mPlayerFactory = playerFactory;
     85         mRinger = ringer;
     86         mRingbackPlayer = ringbackPlayer;
     87         mDtmfLocalTonePlayer = dtmfLocalTonePlayer;
     88 
     89         mPlayerFactory.setCallAudioManager(this);
     90         mCallAudioModeStateMachine.setCallAudioManager(this);
     91     }
     92 
     93     @Override
     94     public void onCallStateChanged(Call call, int oldState, int newState) {
     95         if (shouldIgnoreCallForAudio(call)) {
     96             // No audio management for calls in a conference, or external calls.
     97             return;
     98         }
     99         Log.d(LOG_TAG, "Call state changed for TC@%s: %s -> %s", call.getId(),
    100                 CallState.toString(oldState), CallState.toString(newState));
    101 
    102         for (int i = 0; i < mCallStateToCalls.size(); i++) {
    103             mCallStateToCalls.valueAt(i).remove(call);
    104         }
    105         if (mCallStateToCalls.get(newState) != null) {
    106             mCallStateToCalls.get(newState).add(call);
    107         }
    108 
    109         updateForegroundCall();
    110         if (shouldPlayDisconnectTone(oldState, newState)) {
    111             playToneForDisconnectedCall(call);
    112         }
    113 
    114         onCallLeavingState(call, oldState);
    115         onCallEnteringState(call, newState);
    116     }
    117 
    118     @Override
    119     public void onCallAdded(Call call) {
    120         if (shouldIgnoreCallForAudio(call)) {
    121             return; // Don't do audio handling for calls in a conference, or external calls.
    122         }
    123 
    124         addCall(call);
    125     }
    126 
    127     @Override
    128     public void onCallRemoved(Call call) {
    129         if (shouldIgnoreCallForAudio(call)) {
    130             return; // Don't do audio handling for calls in a conference, or external calls.
    131         }
    132 
    133         removeCall(call);
    134     }
    135 
    136     private void addCall(Call call) {
    137         if (mCalls.contains(call)) {
    138             Log.w(LOG_TAG, "Call TC@%s is being added twice.", call.getId());
    139             return; // No guarantees that the same call won't get added twice.
    140         }
    141 
    142         Log.d(LOG_TAG, "Call added with id TC@%s in state %s", call.getId(),
    143                 CallState.toString(call.getState()));
    144 
    145         if (mCallStateToCalls.get(call.getState()) != null) {
    146             mCallStateToCalls.get(call.getState()).add(call);
    147         }
    148         updateForegroundCall();
    149         mCalls.add(call);
    150 
    151         onCallEnteringState(call, call.getState());
    152     }
    153 
    154     private void removeCall(Call call) {
    155         if (!mCalls.contains(call)) {
    156             return; // No guarantees that the same call won't get removed twice.
    157         }
    158 
    159         Log.d(LOG_TAG, "Call removed with id TC@%s in state %s", call.getId(),
    160                 CallState.toString(call.getState()));
    161 
    162         for (int i = 0; i < mCallStateToCalls.size(); i++) {
    163             mCallStateToCalls.valueAt(i).remove(call);
    164         }
    165 
    166         updateForegroundCall();
    167         mCalls.remove(call);
    168 
    169         onCallLeavingState(call, call.getState());
    170     }
    171 
    172     /**
    173      * Handles changes to the external state of a call.  External calls which become regular calls
    174      * should be tracked, and regular calls which become external should no longer be tracked.
    175      *
    176      * @param call The call.
    177      * @param isExternalCall {@code True} if the call is now external, {@code false} if it is now
    178      *      a regular call.
    179      */
    180     @Override
    181     public void onExternalCallChanged(Call call, boolean isExternalCall) {
    182         if (isExternalCall) {
    183             Log.d(LOG_TAG, "Removing call which became external ID %s", call.getId());
    184             removeCall(call);
    185         } else if (!isExternalCall) {
    186             Log.d(LOG_TAG, "Adding external call which was pulled with ID %s", call.getId());
    187             addCall(call);
    188 
    189             if (mCallsManager.isSpeakerphoneAutoEnabledForVideoCalls(call.getVideoState())) {
    190                 // When pulling a video call, automatically enable the speakerphone.
    191                 Log.d(LOG_TAG, "Switching to speaker because external video call %s was pulled." +
    192                         call.getId());
    193                 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
    194                         CallAudioRouteStateMachine.SWITCH_SPEAKER);
    195             }
    196         }
    197     }
    198 
    199     /**
    200      * Determines if {@link CallAudioManager} should do any audio routing operations for a call.
    201      * We ignore child calls of a conference and external calls for audio routing purposes.
    202      *
    203      * @param call The call to check.
    204      * @return {@code true} if the call should be ignored for audio routing, {@code false}
    205      * otherwise
    206      */
    207     private boolean shouldIgnoreCallForAudio(Call call) {
    208         return call.getParentCall() != null || call.isExternalCall();
    209     }
    210 
    211     @Override
    212     public void onIncomingCallAnswered(Call call) {
    213         if (!mCalls.contains(call)) {
    214             return;
    215         }
    216 
    217         // This is called after the UI answers the call, but before the connection service
    218         // sets the call to active. Only thing to handle for mode here is the audio speedup thing.
    219 
    220         if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) {
    221             if (mForegroundCall == call) {
    222                 Log.i(LOG_TAG, "Invoking the MT_AUDIO_SPEEDUP mechanism. Transitioning into " +
    223                         "an active in-call audio state before connection service has " +
    224                         "connected the call.");
    225                 if (mCallStateToCalls.get(call.getState()) != null) {
    226                     mCallStateToCalls.get(call.getState()).remove(call);
    227                 }
    228                 mActiveDialingOrConnectingCalls.add(call);
    229                 mCallAudioModeStateMachine.sendMessageWithArgs(
    230                         CallAudioModeStateMachine.MT_AUDIO_SPEEDUP_FOR_RINGING_CALL,
    231                         makeArgsForModeStateMachine());
    232             }
    233         }
    234 
    235         // Turn off mute when a new incoming call is answered.
    236         mute(false /* shouldMute */);
    237 
    238         maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call);
    239     }
    240 
    241     @Override
    242     public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) {
    243         if (videoProfile == null) {
    244             return;
    245         }
    246 
    247         if (call != mForegroundCall) {
    248             // We only play tones for foreground calls.
    249             return;
    250         }
    251 
    252         int previousVideoState = call.getVideoState();
    253         int newVideoState = videoProfile.getVideoState();
    254         Log.v(this, "onSessionModifyRequestReceived : videoProfile = " + VideoProfile
    255                 .videoStateToString(newVideoState));
    256 
    257         boolean isUpgradeRequest = !VideoProfile.isReceptionEnabled(previousVideoState) &&
    258                 VideoProfile.isReceptionEnabled(newVideoState);
    259 
    260         if (isUpgradeRequest) {
    261             mPlayerFactory.createPlayer(InCallTonePlayer.TONE_VIDEO_UPGRADE).startTone();
    262         }
    263     }
    264 
    265     /**
    266      * Play or stop a call hold tone for a call.  Triggered via
    267      * {@link Connection#sendConnectionEvent(String)} when the
    268      * {@link Connection#EVENT_ON_HOLD_TONE_START} event or
    269      * {@link Connection#EVENT_ON_HOLD_TONE_STOP} event is passed through to the
    270      *
    271      * @param call The call which requested the hold tone.
    272      */
    273     @Override
    274     public void onHoldToneRequested(Call call) {
    275         maybePlayHoldTone();
    276     }
    277 
    278     @Override
    279     public void onIsVoipAudioModeChanged(Call call) {
    280         if (call != mForegroundCall) {
    281             return;
    282         }
    283         mCallAudioModeStateMachine.sendMessageWithArgs(
    284                 CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE,
    285                 makeArgsForModeStateMachine());
    286     }
    287 
    288     @Override
    289     public void onRingbackRequested(Call call, boolean shouldRingback) {
    290         if (call == mForegroundCall && shouldRingback) {
    291             mRingbackPlayer.startRingbackForCall(call);
    292         } else {
    293             mRingbackPlayer.stopRingbackForCall(call);
    294         }
    295     }
    296 
    297     @Override
    298     public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String message) {
    299         maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call);
    300     }
    301 
    302     @Override
    303     public void onIsConferencedChanged(Call call) {
    304         // This indicates a conferencing change, which shouldn't impact any audio mode stuff.
    305         Call parentCall = call.getParentCall();
    306         if (parentCall == null) {
    307             // Indicates that the call should be tracked for audio purposes. Treat it as if it were
    308             // just added.
    309             Log.i(LOG_TAG, "Call TC@" + call.getId() + " left conference and will" +
    310                             " now be tracked by CallAudioManager.");
    311             onCallAdded(call);
    312         } else {
    313             // The call joined a conference, so stop tracking it.
    314             if (mCallStateToCalls.get(call.getState()) != null) {
    315                 mCallStateToCalls.get(call.getState()).remove(call);
    316             }
    317 
    318             updateForegroundCall();
    319             mCalls.remove(call);
    320         }
    321     }
    322 
    323     @Override
    324     public void onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs,
    325             ConnectionServiceWrapper newCs) {
    326         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
    327                 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
    328     }
    329 
    330     @Override
    331     public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {
    332         if (call != getForegroundCall()) {
    333             Log.d(LOG_TAG, "Ignoring video state change from %s to %s for call %s -- not " +
    334                     "foreground.", VideoProfile.videoStateToString(previousVideoState),
    335                     VideoProfile.videoStateToString(newVideoState), call.getId());
    336             return;
    337         }
    338 
    339         if (!VideoProfile.isVideo(previousVideoState) &&
    340                 mCallsManager.isSpeakerphoneAutoEnabledForVideoCalls(newVideoState)) {
    341             Log.d(LOG_TAG, "Switching to speaker because call %s transitioned video state from %s" +
    342                     " to %s", call.getId(), VideoProfile.videoStateToString(previousVideoState),
    343                     VideoProfile.videoStateToString(newVideoState));
    344             mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
    345                     CallAudioRouteStateMachine.SWITCH_SPEAKER);
    346         }
    347     }
    348 
    349     public CallAudioState getCallAudioState() {
    350         return mCallAudioRouteStateMachine.getCurrentCallAudioState();
    351     }
    352 
    353     public Call getPossiblyHeldForegroundCall() {
    354         return mForegroundCall;
    355     }
    356 
    357     public Call getForegroundCall() {
    358         if (mForegroundCall != null && mForegroundCall.getState() != CallState.ON_HOLD) {
    359             return mForegroundCall;
    360         }
    361         return null;
    362     }
    363 
    364     void toggleMute() {
    365         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
    366                 CallAudioRouteStateMachine.TOGGLE_MUTE);
    367     }
    368 
    369     @VisibleForTesting
    370     public void mute(boolean shouldMute) {
    371         Log.v(this, "mute, shouldMute: %b", shouldMute);
    372 
    373         // Don't mute if there are any emergency calls.
    374         if (mCallsManager.hasEmergencyCall()) {
    375             shouldMute = false;
    376             Log.v(this, "ignoring mute for emergency call");
    377         }
    378 
    379         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(shouldMute
    380                 ? CallAudioRouteStateMachine.MUTE_ON : CallAudioRouteStateMachine.MUTE_OFF);
    381     }
    382 
    383     /**
    384      * Changed the audio route, for example from earpiece to speaker phone.
    385      *
    386      * @param route The new audio route to use. See {@link CallAudioState}.
    387      */
    388     void setAudioRoute(int route) {
    389         Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route));
    390         switch (route) {
    391             case CallAudioState.ROUTE_BLUETOOTH:
    392                 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
    393                         CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH);
    394                 return;
    395             case CallAudioState.ROUTE_SPEAKER:
    396                 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
    397                         CallAudioRouteStateMachine.USER_SWITCH_SPEAKER);
    398                 return;
    399             case CallAudioState.ROUTE_WIRED_HEADSET:
    400                 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
    401                         CallAudioRouteStateMachine.USER_SWITCH_HEADSET);
    402                 return;
    403             case CallAudioState.ROUTE_EARPIECE:
    404                 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
    405                         CallAudioRouteStateMachine.USER_SWITCH_EARPIECE);
    406                 return;
    407             case CallAudioState.ROUTE_WIRED_OR_EARPIECE:
    408                 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
    409                         CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE);
    410                 return;
    411             default:
    412                 Log.wtf(this, "Invalid route specified: %d", route);
    413         }
    414     }
    415 
    416     void silenceRingers() {
    417         for (Call call : mRingingCalls) {
    418             call.silence();
    419         }
    420 
    421         mRinger.stopRinging();
    422         mRinger.stopCallWaiting();
    423     }
    424 
    425     @VisibleForTesting
    426     public boolean startRinging() {
    427         return mRinger.startRinging(mForegroundCall,
    428                 mCallAudioRouteStateMachine.isHfpDeviceAvailable());
    429     }
    430 
    431     @VisibleForTesting
    432     public void startCallWaiting() {
    433         mRinger.startCallWaiting(mRingingCalls.iterator().next());
    434     }
    435 
    436     @VisibleForTesting
    437     public void stopRinging() {
    438         mRinger.stopRinging();
    439     }
    440 
    441     @VisibleForTesting
    442     public void stopCallWaiting() {
    443         mRinger.stopCallWaiting();
    444     }
    445 
    446     @VisibleForTesting
    447     public void setCallAudioRouteFocusState(int focusState) {
    448         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
    449                 CallAudioRouteStateMachine.SWITCH_FOCUS, focusState);
    450     }
    451 
    452     @VisibleForTesting
    453     public CallAudioRouteStateMachine getCallAudioRouteStateMachine() {
    454         return mCallAudioRouteStateMachine;
    455     }
    456 
    457     @VisibleForTesting
    458     public CallAudioModeStateMachine getCallAudioModeStateMachine() {
    459         return mCallAudioModeStateMachine;
    460     }
    461 
    462     void dump(IndentingPrintWriter pw) {
    463         pw.println("All calls:");
    464         pw.increaseIndent();
    465         dumpCallsInCollection(pw, mCalls);
    466         pw.decreaseIndent();
    467 
    468         pw.println("Active dialing, or connecting calls:");
    469         pw.increaseIndent();
    470         dumpCallsInCollection(pw, mActiveDialingOrConnectingCalls);
    471         pw.decreaseIndent();
    472 
    473         pw.println("Ringing calls:");
    474         pw.increaseIndent();
    475         dumpCallsInCollection(pw, mRingingCalls);
    476         pw.decreaseIndent();
    477 
    478         pw.println("Holding calls:");
    479         pw.increaseIndent();
    480         dumpCallsInCollection(pw, mHoldingCalls);
    481         pw.decreaseIndent();
    482 
    483         pw.println("Foreground call:");
    484         pw.println(mForegroundCall);
    485 
    486         pw.println("CallAudioModeStateMachine pending messages:");
    487         pw.increaseIndent();
    488         mCallAudioModeStateMachine.dumpPendingMessages(pw);
    489         pw.decreaseIndent();
    490 
    491         pw.println("CallAudioRouteStateMachine pending messages:");
    492         pw.increaseIndent();
    493         mCallAudioRouteStateMachine.dumpPendingMessages(pw);
    494         pw.decreaseIndent();
    495     }
    496 
    497     @VisibleForTesting
    498     public void setIsTonePlaying(boolean isTonePlaying) {
    499         mIsTonePlaying = isTonePlaying;
    500         mCallAudioModeStateMachine.sendMessageWithArgs(
    501                 isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING
    502                         : CallAudioModeStateMachine.TONE_STOPPED_PLAYING,
    503                 makeArgsForModeStateMachine());
    504     }
    505 
    506     private void onCallLeavingState(Call call, int state) {
    507         switch (state) {
    508             case CallState.ACTIVE:
    509             case CallState.CONNECTING:
    510                 onCallLeavingActiveDialingOrConnecting();
    511                 break;
    512             case CallState.RINGING:
    513                 onCallLeavingRinging();
    514                 break;
    515             case CallState.ON_HOLD:
    516                 onCallLeavingHold();
    517                 break;
    518             case CallState.PULLING:
    519                 onCallLeavingActiveDialingOrConnecting();
    520                 break;
    521             case CallState.DIALING:
    522                 stopRingbackForCall(call);
    523                 onCallLeavingActiveDialingOrConnecting();
    524                 break;
    525         }
    526     }
    527 
    528     private void onCallEnteringState(Call call, int state) {
    529         switch (state) {
    530             case CallState.ACTIVE:
    531             case CallState.CONNECTING:
    532                 onCallEnteringActiveDialingOrConnecting();
    533                 break;
    534             case CallState.RINGING:
    535                 onCallEnteringRinging();
    536                 break;
    537             case CallState.ON_HOLD:
    538                 onCallEnteringHold();
    539                 break;
    540             case CallState.PULLING:
    541                 onCallEnteringActiveDialingOrConnecting();
    542                 break;
    543             case CallState.DIALING:
    544                 onCallEnteringActiveDialingOrConnecting();
    545                 playRingbackForCall(call);
    546                 break;
    547         }
    548     }
    549 
    550     private void onCallLeavingActiveDialingOrConnecting() {
    551         if (mActiveDialingOrConnectingCalls.size() == 0) {
    552             mCallAudioModeStateMachine.sendMessageWithArgs(
    553                     CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS,
    554                     makeArgsForModeStateMachine());
    555         }
    556     }
    557 
    558     private void onCallLeavingRinging() {
    559         if (mRingingCalls.size() == 0) {
    560             mCallAudioModeStateMachine.sendMessageWithArgs(
    561                     CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
    562                     makeArgsForModeStateMachine());
    563         }
    564     }
    565 
    566     private void onCallLeavingHold() {
    567         if (mHoldingCalls.size() == 0) {
    568             mCallAudioModeStateMachine.sendMessageWithArgs(
    569                     CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS,
    570                     makeArgsForModeStateMachine());
    571         }
    572     }
    573 
    574     private void onCallEnteringActiveDialingOrConnecting() {
    575         if (mActiveDialingOrConnectingCalls.size() == 1) {
    576             mCallAudioModeStateMachine.sendMessageWithArgs(
    577                     CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL,
    578                     makeArgsForModeStateMachine());
    579         }
    580     }
    581 
    582     private void onCallEnteringRinging() {
    583         if (mRingingCalls.size() == 1) {
    584             mCallAudioModeStateMachine.sendMessageWithArgs(
    585                     CallAudioModeStateMachine.NEW_RINGING_CALL,
    586                     makeArgsForModeStateMachine());
    587         }
    588     }
    589 
    590     private void onCallEnteringHold() {
    591         if (mHoldingCalls.size() == 1) {
    592             mCallAudioModeStateMachine.sendMessageWithArgs(
    593                     CallAudioModeStateMachine.NEW_HOLDING_CALL,
    594                     makeArgsForModeStateMachine());
    595         }
    596     }
    597 
    598     private void updateForegroundCall() {
    599         Call oldForegroundCall = mForegroundCall;
    600         if (mActiveDialingOrConnectingCalls.size() > 0) {
    601             // Give preference for connecting calls over active/dialing for foreground-ness.
    602             Call possibleConnectingCall = null;
    603             for (Call call : mActiveDialingOrConnectingCalls) {
    604                 if (call.getState() == CallState.CONNECTING) {
    605                     possibleConnectingCall = call;
    606                 }
    607             }
    608             mForegroundCall = possibleConnectingCall == null ?
    609                     mActiveDialingOrConnectingCalls.iterator().next() : possibleConnectingCall;
    610         } else if (mRingingCalls.size() > 0) {
    611             mForegroundCall = mRingingCalls.iterator().next();
    612         } else if (mHoldingCalls.size() > 0) {
    613             mForegroundCall = mHoldingCalls.iterator().next();
    614         } else {
    615             mForegroundCall = null;
    616         }
    617 
    618         if (mForegroundCall != oldForegroundCall) {
    619             mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
    620                     CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
    621             mDtmfLocalTonePlayer.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
    622             maybePlayHoldTone();
    623         }
    624     }
    625 
    626     @NonNull
    627     private CallAudioModeStateMachine.MessageArgs makeArgsForModeStateMachine() {
    628         return new CallAudioModeStateMachine.MessageArgs(
    629                 mActiveDialingOrConnectingCalls.size() > 0,
    630                 mRingingCalls.size() > 0,
    631                 mHoldingCalls.size() > 0,
    632                 mIsTonePlaying,
    633                 mForegroundCall != null && mForegroundCall.getIsVoipAudioMode(),
    634                 Log.createSubsession());
    635     }
    636 
    637     private void playToneForDisconnectedCall(Call call) {
    638         if (mForegroundCall != null && call != mForegroundCall && mCalls.size() > 1) {
    639             Log.v(LOG_TAG, "Omitting tone because we are not foreground" +
    640                     " and there is another call.");
    641             return;
    642         }
    643 
    644         if (call.getDisconnectCause() != null) {
    645             int toneToPlay = InCallTonePlayer.TONE_INVALID;
    646 
    647             Log.v(this, "Disconnect cause: %s.", call.getDisconnectCause());
    648 
    649             switch(call.getDisconnectCause().getTone()) {
    650                 case ToneGenerator.TONE_SUP_BUSY:
    651                     toneToPlay = InCallTonePlayer.TONE_BUSY;
    652                     break;
    653                 case ToneGenerator.TONE_SUP_CONGESTION:
    654                     toneToPlay = InCallTonePlayer.TONE_CONGESTION;
    655                     break;
    656                 case ToneGenerator.TONE_CDMA_REORDER:
    657                     toneToPlay = InCallTonePlayer.TONE_REORDER;
    658                     break;
    659                 case ToneGenerator.TONE_CDMA_ABBR_INTERCEPT:
    660                     toneToPlay = InCallTonePlayer.TONE_INTERCEPT;
    661                     break;
    662                 case ToneGenerator.TONE_CDMA_CALLDROP_LITE:
    663                     toneToPlay = InCallTonePlayer.TONE_CDMA_DROP;
    664                     break;
    665                 case ToneGenerator.TONE_SUP_ERROR:
    666                     toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER;
    667                     break;
    668                 case ToneGenerator.TONE_PROP_PROMPT:
    669                     toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
    670                     break;
    671             }
    672 
    673             Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay);
    674 
    675             if (toneToPlay != InCallTonePlayer.TONE_INVALID) {
    676                 mPlayerFactory.createPlayer(toneToPlay).startTone();
    677             }
    678         }
    679     }
    680 
    681     private void playRingbackForCall(Call call) {
    682         if (call == mForegroundCall && call.isRingbackRequested()) {
    683             mRingbackPlayer.startRingbackForCall(call);
    684         }
    685     }
    686 
    687     private void stopRingbackForCall(Call call) {
    688         mRingbackPlayer.stopRingbackForCall(call);
    689     }
    690 
    691     /**
    692      * Determines if a hold tone should be played and then starts or stops it accordingly.
    693      */
    694     private void maybePlayHoldTone() {
    695         if (shouldPlayHoldTone()) {
    696             if (mHoldTonePlayer == null) {
    697                 mHoldTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
    698                 mHoldTonePlayer.startTone();
    699             }
    700         } else {
    701             if (mHoldTonePlayer != null) {
    702                 mHoldTonePlayer.stopTone();
    703                 mHoldTonePlayer = null;
    704             }
    705         }
    706     }
    707 
    708     /**
    709      * Determines if a hold tone should be played.
    710      * A hold tone should be played only if foreground call is equals with call which is
    711      * remotely held.
    712      *
    713      * @return {@code true} if the the hold tone should be played, {@code false} otherwise.
    714      */
    715     private boolean shouldPlayHoldTone() {
    716         Call foregroundCall = getForegroundCall();
    717         // If there is no foreground call, no hold tone should play.
    718         if (foregroundCall == null) {
    719             return false;
    720         }
    721 
    722         // If another call is ringing, no hold tone should play.
    723         if (mCallsManager.hasRingingCall()) {
    724             return false;
    725         }
    726 
    727         // If the foreground call isn't active, no hold tone should play. This might happen, for
    728         // example, if the user puts a remotely held call on hold itself.
    729         if (!foregroundCall.isActive()) {
    730             return false;
    731         }
    732 
    733         return foregroundCall.isRemotelyHeld();
    734     }
    735 
    736     private void dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls) {
    737         for (Call call : calls) {
    738             if (call != null) pw.println(call.getId());
    739         }
    740     }
    741 
    742     private void maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(Call call) {
    743         // Check to see if the call being answered/rejected is the only ringing call, since this
    744         // will be called before the connection service acknowledges the state change.
    745         if (mRingingCalls.size() == 0 ||
    746                 (mRingingCalls.size() == 1 && call == mRingingCalls.iterator().next())) {
    747             mRinger.stopRinging();
    748             mRinger.stopCallWaiting();
    749         }
    750     }
    751 
    752     private boolean shouldPlayDisconnectTone(int oldState, int newState) {
    753         if (newState != CallState.DISCONNECTED) {
    754             return false;
    755         }
    756         return oldState == CallState.ACTIVE ||
    757                 oldState == CallState.DIALING ||
    758                 oldState == CallState.ON_HOLD;
    759     }
    760 
    761     @VisibleForTesting
    762     public Set<Call> getTrackedCalls() {
    763         return mCalls;
    764     }
    765 
    766     @VisibleForTesting
    767     public SparseArray<LinkedHashSet<Call>> getCallStateToCalls() {
    768         return mCallStateToCalls;
    769     }
    770 }