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