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 (shouldPlayDisconnectTone(oldState, newState)) {
    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 boolean startRinging() {
    426         return 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     @VisibleForTesting
    456     public CallAudioModeStateMachine getCallAudioModeStateMachine() {
    457         return mCallAudioModeStateMachine;
    458     }
    459 
    460     void dump(IndentingPrintWriter pw) {
    461         pw.println("All calls:");
    462         pw.increaseIndent();
    463         dumpCallsInCollection(pw, mCalls);
    464         pw.decreaseIndent();
    465 
    466         pw.println("Active dialing, or connecting calls:");
    467         pw.increaseIndent();
    468         dumpCallsInCollection(pw, mActiveDialingOrConnectingCalls);
    469         pw.decreaseIndent();
    470 
    471         pw.println("Ringing calls:");
    472         pw.increaseIndent();
    473         dumpCallsInCollection(pw, mRingingCalls);
    474         pw.decreaseIndent();
    475 
    476         pw.println("Holding calls:");
    477         pw.increaseIndent();
    478         dumpCallsInCollection(pw, mHoldingCalls);
    479         pw.decreaseIndent();
    480 
    481         pw.println("Foreground call:");
    482         pw.println(mForegroundCall);
    483     }
    484 
    485     @VisibleForTesting
    486     public void setIsTonePlaying(boolean isTonePlaying) {
    487         mIsTonePlaying = isTonePlaying;
    488         mCallAudioModeStateMachine.sendMessageWithArgs(
    489                 isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING
    490                         : CallAudioModeStateMachine.TONE_STOPPED_PLAYING,
    491                 makeArgsForModeStateMachine());
    492     }
    493 
    494     private void onCallLeavingState(Call call, int state) {
    495         switch (state) {
    496             case CallState.ACTIVE:
    497             case CallState.CONNECTING:
    498                 onCallLeavingActiveDialingOrConnecting();
    499                 break;
    500             case CallState.RINGING:
    501                 onCallLeavingRinging();
    502                 break;
    503             case CallState.ON_HOLD:
    504                 onCallLeavingHold();
    505                 break;
    506             case CallState.PULLING:
    507                 onCallLeavingActiveDialingOrConnecting();
    508                 break;
    509             case CallState.DIALING:
    510                 stopRingbackForCall(call);
    511                 onCallLeavingActiveDialingOrConnecting();
    512                 break;
    513         }
    514     }
    515 
    516     private void onCallEnteringState(Call call, int state) {
    517         switch (state) {
    518             case CallState.ACTIVE:
    519             case CallState.CONNECTING:
    520                 onCallEnteringActiveDialingOrConnecting();
    521                 break;
    522             case CallState.RINGING:
    523                 onCallEnteringRinging();
    524                 break;
    525             case CallState.ON_HOLD:
    526                 onCallEnteringHold();
    527                 break;
    528             case CallState.PULLING:
    529                 onCallEnteringActiveDialingOrConnecting();
    530                 break;
    531             case CallState.DIALING:
    532                 onCallEnteringActiveDialingOrConnecting();
    533                 playRingbackForCall(call);
    534                 break;
    535         }
    536     }
    537 
    538     private void onCallLeavingActiveDialingOrConnecting() {
    539         if (mActiveDialingOrConnectingCalls.size() == 0) {
    540             mCallAudioModeStateMachine.sendMessageWithArgs(
    541                     CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS,
    542                     makeArgsForModeStateMachine());
    543         }
    544     }
    545 
    546     private void onCallLeavingRinging() {
    547         if (mRingingCalls.size() == 0) {
    548             mCallAudioModeStateMachine.sendMessageWithArgs(
    549                     CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
    550                     makeArgsForModeStateMachine());
    551         }
    552     }
    553 
    554     private void onCallLeavingHold() {
    555         if (mHoldingCalls.size() == 0) {
    556             mCallAudioModeStateMachine.sendMessageWithArgs(
    557                     CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS,
    558                     makeArgsForModeStateMachine());
    559         }
    560     }
    561 
    562     private void onCallEnteringActiveDialingOrConnecting() {
    563         if (mActiveDialingOrConnectingCalls.size() == 1) {
    564             mCallAudioModeStateMachine.sendMessageWithArgs(
    565                     CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL,
    566                     makeArgsForModeStateMachine());
    567         }
    568     }
    569 
    570     private void onCallEnteringRinging() {
    571         if (mRingingCalls.size() == 1) {
    572             mCallAudioModeStateMachine.sendMessageWithArgs(
    573                     CallAudioModeStateMachine.NEW_RINGING_CALL,
    574                     makeArgsForModeStateMachine());
    575         }
    576     }
    577 
    578     private void onCallEnteringHold() {
    579         if (mHoldingCalls.size() == 1) {
    580             mCallAudioModeStateMachine.sendMessageWithArgs(
    581                     CallAudioModeStateMachine.NEW_HOLDING_CALL,
    582                     makeArgsForModeStateMachine());
    583         }
    584     }
    585 
    586     private void updateForegroundCall() {
    587         Call oldForegroundCall = mForegroundCall;
    588         if (mActiveDialingOrConnectingCalls.size() > 0) {
    589             // Give preference for connecting calls over active/dialing for foreground-ness.
    590             Call possibleConnectingCall = null;
    591             for (Call call : mActiveDialingOrConnectingCalls) {
    592                 if (call.getState() == CallState.CONNECTING) {
    593                     possibleConnectingCall = call;
    594                 }
    595             }
    596             mForegroundCall = possibleConnectingCall == null ?
    597                     mActiveDialingOrConnectingCalls.iterator().next() : possibleConnectingCall;
    598         } else if (mRingingCalls.size() > 0) {
    599             mForegroundCall = mRingingCalls.iterator().next();
    600         } else if (mHoldingCalls.size() > 0) {
    601             mForegroundCall = mHoldingCalls.iterator().next();
    602         } else {
    603             mForegroundCall = null;
    604         }
    605 
    606         if (mForegroundCall != oldForegroundCall) {
    607             mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
    608                     CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
    609             mDtmfLocalTonePlayer.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
    610             maybePlayHoldTone();
    611         }
    612     }
    613 
    614     @NonNull
    615     private CallAudioModeStateMachine.MessageArgs makeArgsForModeStateMachine() {
    616         return new CallAudioModeStateMachine.MessageArgs(
    617                 mActiveDialingOrConnectingCalls.size() > 0,
    618                 mRingingCalls.size() > 0,
    619                 mHoldingCalls.size() > 0,
    620                 mIsTonePlaying,
    621                 mForegroundCall != null && mForegroundCall.getIsVoipAudioMode(),
    622                 Log.createSubsession());
    623     }
    624 
    625     private void playToneForDisconnectedCall(Call call) {
    626         if (mForegroundCall != null && call != mForegroundCall && mCalls.size() > 1) {
    627             Log.v(LOG_TAG, "Omitting tone because we are not foreground" +
    628                     " and there is another call.");
    629             return;
    630         }
    631 
    632         if (call.getDisconnectCause() != null) {
    633             int toneToPlay = InCallTonePlayer.TONE_INVALID;
    634 
    635             Log.v(this, "Disconnect cause: %s.", call.getDisconnectCause());
    636 
    637             switch(call.getDisconnectCause().getTone()) {
    638                 case ToneGenerator.TONE_SUP_BUSY:
    639                     toneToPlay = InCallTonePlayer.TONE_BUSY;
    640                     break;
    641                 case ToneGenerator.TONE_SUP_CONGESTION:
    642                     toneToPlay = InCallTonePlayer.TONE_CONGESTION;
    643                     break;
    644                 case ToneGenerator.TONE_CDMA_REORDER:
    645                     toneToPlay = InCallTonePlayer.TONE_REORDER;
    646                     break;
    647                 case ToneGenerator.TONE_CDMA_ABBR_INTERCEPT:
    648                     toneToPlay = InCallTonePlayer.TONE_INTERCEPT;
    649                     break;
    650                 case ToneGenerator.TONE_CDMA_CALLDROP_LITE:
    651                     toneToPlay = InCallTonePlayer.TONE_CDMA_DROP;
    652                     break;
    653                 case ToneGenerator.TONE_SUP_ERROR:
    654                     toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER;
    655                     break;
    656                 case ToneGenerator.TONE_PROP_PROMPT:
    657                     toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
    658                     break;
    659             }
    660 
    661             Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay);
    662 
    663             if (toneToPlay != InCallTonePlayer.TONE_INVALID) {
    664                 mPlayerFactory.createPlayer(toneToPlay).startTone();
    665             }
    666         }
    667     }
    668 
    669     private void playRingbackForCall(Call call) {
    670         if (call == mForegroundCall && call.isRingbackRequested()) {
    671             mRingbackPlayer.startRingbackForCall(call);
    672         }
    673     }
    674 
    675     private void stopRingbackForCall(Call call) {
    676         mRingbackPlayer.stopRingbackForCall(call);
    677     }
    678 
    679     /**
    680      * Determines if a hold tone should be played and then starts or stops it accordingly.
    681      */
    682     private void maybePlayHoldTone() {
    683         if (shouldPlayHoldTone()) {
    684             if (mHoldTonePlayer == null) {
    685                 mHoldTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
    686                 mHoldTonePlayer.startTone();
    687             }
    688         } else {
    689             if (mHoldTonePlayer != null) {
    690                 mHoldTonePlayer.stopTone();
    691                 mHoldTonePlayer = null;
    692             }
    693         }
    694     }
    695 
    696     /**
    697      * Determines if a hold tone should be played.
    698      * A hold tone should be played only if foreground call is equals with call which is
    699      * remotely held.
    700      *
    701      * @return {@code true} if the the hold tone should be played, {@code false} otherwise.
    702      */
    703     private boolean shouldPlayHoldTone() {
    704         Call foregroundCall = getForegroundCall();
    705         // If there is no foreground call, no hold tone should play.
    706         if (foregroundCall == null) {
    707             return false;
    708         }
    709 
    710         // If another call is ringing, no hold tone should play.
    711         if (mCallsManager.hasRingingCall()) {
    712             return false;
    713         }
    714 
    715         // If the foreground call isn't active, no hold tone should play. This might happen, for
    716         // example, if the user puts a remotely held call on hold itself.
    717         if (!foregroundCall.isActive()) {
    718             return false;
    719         }
    720 
    721         return foregroundCall.isRemotelyHeld();
    722     }
    723 
    724     private void dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls) {
    725         for (Call call : calls) {
    726             if (call != null) pw.println(call.getId());
    727         }
    728     }
    729 
    730     private void maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(Call call) {
    731         // Check to see if the call being answered/rejected is the only ringing call, since this
    732         // will be called before the connection service acknowledges the state change.
    733         if (mRingingCalls.size() == 0 ||
    734                 (mRingingCalls.size() == 1 && call == mRingingCalls.iterator().next())) {
    735             mRinger.stopRinging();
    736             mRinger.stopCallWaiting();
    737         }
    738     }
    739 
    740     private boolean shouldPlayDisconnectTone(int oldState, int newState) {
    741         if (newState != CallState.DISCONNECTED) {
    742             return false;
    743         }
    744         return oldState == CallState.ACTIVE ||
    745                 oldState == CallState.DIALING ||
    746                 oldState == CallState.ON_HOLD;
    747     }
    748 
    749     @VisibleForTesting
    750     public Set<Call> getTrackedCalls() {
    751         return mCalls;
    752     }
    753 
    754     @VisibleForTesting
    755     public SparseArray<LinkedHashSet<Call>> getCallStateToCalls() {
    756         return mCallStateToCalls;
    757     }
    758 }