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 iff it's not a handover.
    236         if (!call.isHandoverInProgress()) {
    237             mute(false /* shouldMute */);
    238         }
    239 
    240         maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call);
    241     }
    242 
    243     @Override
    244     public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) {
    245         if (videoProfile == null) {
    246             return;
    247         }
    248 
    249         if (call != mForegroundCall) {
    250             // We only play tones for foreground calls.
    251             return;
    252         }
    253 
    254         int previousVideoState = call.getVideoState();
    255         int newVideoState = videoProfile.getVideoState();
    256         Log.v(this, "onSessionModifyRequestReceived : videoProfile = " + VideoProfile
    257                 .videoStateToString(newVideoState));
    258 
    259         boolean isUpgradeRequest = !VideoProfile.isReceptionEnabled(previousVideoState) &&
    260                 VideoProfile.isReceptionEnabled(newVideoState);
    261 
    262         if (isUpgradeRequest) {
    263             mPlayerFactory.createPlayer(InCallTonePlayer.TONE_VIDEO_UPGRADE).startTone();
    264         }
    265     }
    266 
    267     /**
    268      * Play or stop a call hold tone for a call.  Triggered via
    269      * {@link Connection#sendConnectionEvent(String)} when the
    270      * {@link Connection#EVENT_ON_HOLD_TONE_START} event or
    271      * {@link Connection#EVENT_ON_HOLD_TONE_STOP} event is passed through to the
    272      *
    273      * @param call The call which requested the hold tone.
    274      */
    275     @Override
    276     public void onHoldToneRequested(Call call) {
    277         maybePlayHoldTone();
    278     }
    279 
    280     @Override
    281     public void onIsVoipAudioModeChanged(Call call) {
    282         if (call != mForegroundCall) {
    283             return;
    284         }
    285         mCallAudioModeStateMachine.sendMessageWithArgs(
    286                 CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE,
    287                 makeArgsForModeStateMachine());
    288     }
    289 
    290     @Override
    291     public void onRingbackRequested(Call call, boolean shouldRingback) {
    292         if (call == mForegroundCall && shouldRingback) {
    293             mRingbackPlayer.startRingbackForCall(call);
    294         } else {
    295             mRingbackPlayer.stopRingbackForCall(call);
    296         }
    297     }
    298 
    299     @Override
    300     public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String message) {
    301         maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call);
    302     }
    303 
    304     @Override
    305     public void onIsConferencedChanged(Call call) {
    306         // This indicates a conferencing change, which shouldn't impact any audio mode stuff.
    307         Call parentCall = call.getParentCall();
    308         if (parentCall == null) {
    309             // Indicates that the call should be tracked for audio purposes. Treat it as if it were
    310             // just added.
    311             Log.i(LOG_TAG, "Call TC@" + call.getId() + " left conference and will" +
    312                             " now be tracked by CallAudioManager.");
    313             onCallAdded(call);
    314         } else {
    315             // The call joined a conference, so stop tracking it.
    316             if (mCallStateToCalls.get(call.getState()) != null) {
    317                 mCallStateToCalls.get(call.getState()).remove(call);
    318             }
    319 
    320             updateForegroundCall();
    321             mCalls.remove(call);
    322         }
    323     }
    324 
    325     @Override
    326     public void onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs,
    327             ConnectionServiceWrapper newCs) {
    328         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
    329                 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
    330     }
    331 
    332     @Override
    333     public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {
    334         if (call != getForegroundCall()) {
    335             Log.d(LOG_TAG, "Ignoring video state change from %s to %s for call %s -- not " +
    336                     "foreground.", VideoProfile.videoStateToString(previousVideoState),
    337                     VideoProfile.videoStateToString(newVideoState), call.getId());
    338             return;
    339         }
    340 
    341         if (!VideoProfile.isVideo(previousVideoState) &&
    342                 mCallsManager.isSpeakerphoneAutoEnabledForVideoCalls(newVideoState)) {
    343             Log.d(LOG_TAG, "Switching to speaker because call %s transitioned video state from %s" +
    344                     " to %s", call.getId(), VideoProfile.videoStateToString(previousVideoState),
    345                     VideoProfile.videoStateToString(newVideoState));
    346             mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
    347                     CallAudioRouteStateMachine.SWITCH_SPEAKER);
    348         }
    349     }
    350 
    351     public CallAudioState getCallAudioState() {
    352         return mCallAudioRouteStateMachine.getCurrentCallAudioState();
    353     }
    354 
    355     public Call getPossiblyHeldForegroundCall() {
    356         return mForegroundCall;
    357     }
    358 
    359     public Call getForegroundCall() {
    360         if (mForegroundCall != null && mForegroundCall.getState() != CallState.ON_HOLD) {
    361             return mForegroundCall;
    362         }
    363         return null;
    364     }
    365 
    366     void toggleMute() {
    367         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
    368                 CallAudioRouteStateMachine.TOGGLE_MUTE);
    369     }
    370 
    371     @VisibleForTesting
    372     public void mute(boolean shouldMute) {
    373         Log.v(this, "mute, shouldMute: %b", shouldMute);
    374 
    375         // Don't mute if there are any emergency calls.
    376         if (mCallsManager.hasEmergencyCall()) {
    377             shouldMute = false;
    378             Log.v(this, "ignoring mute for emergency call");
    379         }
    380 
    381         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(shouldMute
    382                 ? CallAudioRouteStateMachine.MUTE_ON : CallAudioRouteStateMachine.MUTE_OFF);
    383     }
    384 
    385     /**
    386      * Changed the audio route, for example from earpiece to speaker phone.
    387      *
    388      * @param route The new audio route to use. See {@link CallAudioState}.
    389      */
    390     void setAudioRoute(int route) {
    391         Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route));
    392         switch (route) {
    393             case CallAudioState.ROUTE_BLUETOOTH:
    394                 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
    395                         CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH);
    396                 return;
    397             case CallAudioState.ROUTE_SPEAKER:
    398                 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
    399                         CallAudioRouteStateMachine.USER_SWITCH_SPEAKER);
    400                 return;
    401             case CallAudioState.ROUTE_WIRED_HEADSET:
    402                 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
    403                         CallAudioRouteStateMachine.USER_SWITCH_HEADSET);
    404                 return;
    405             case CallAudioState.ROUTE_EARPIECE:
    406                 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
    407                         CallAudioRouteStateMachine.USER_SWITCH_EARPIECE);
    408                 return;
    409             case CallAudioState.ROUTE_WIRED_OR_EARPIECE:
    410                 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
    411                         CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE,
    412                         CallAudioRouteStateMachine.NO_INCLUDE_BLUETOOTH_IN_BASELINE);
    413                 return;
    414             default:
    415                 Log.wtf(this, "Invalid route specified: %d", route);
    416         }
    417     }
    418 
    419     void silenceRingers() {
    420         for (Call call : mRingingCalls) {
    421             call.silence();
    422         }
    423 
    424         mRinger.stopRinging();
    425         mRinger.stopCallWaiting();
    426     }
    427 
    428     @VisibleForTesting
    429     public boolean startRinging() {
    430         return mRinger.startRinging(mForegroundCall,
    431                 mCallAudioRouteStateMachine.isHfpDeviceAvailable());
    432     }
    433 
    434     @VisibleForTesting
    435     public void startCallWaiting() {
    436         if (mRingingCalls.size() == 1) {
    437             mRinger.startCallWaiting(mRingingCalls.iterator().next());
    438         }
    439     }
    440 
    441     @VisibleForTesting
    442     public void stopRinging() {
    443         mRinger.stopRinging();
    444     }
    445 
    446     @VisibleForTesting
    447     public void stopCallWaiting() {
    448         mRinger.stopCallWaiting();
    449     }
    450 
    451     @VisibleForTesting
    452     public void setCallAudioRouteFocusState(int focusState) {
    453         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
    454                 CallAudioRouteStateMachine.SWITCH_FOCUS, focusState);
    455     }
    456 
    457     @VisibleForTesting
    458     public CallAudioRouteStateMachine getCallAudioRouteStateMachine() {
    459         return mCallAudioRouteStateMachine;
    460     }
    461 
    462     @VisibleForTesting
    463     public CallAudioModeStateMachine getCallAudioModeStateMachine() {
    464         return mCallAudioModeStateMachine;
    465     }
    466 
    467     void dump(IndentingPrintWriter pw) {
    468         pw.println("All calls:");
    469         pw.increaseIndent();
    470         dumpCallsInCollection(pw, mCalls);
    471         pw.decreaseIndent();
    472 
    473         pw.println("Active dialing, or connecting calls:");
    474         pw.increaseIndent();
    475         dumpCallsInCollection(pw, mActiveDialingOrConnectingCalls);
    476         pw.decreaseIndent();
    477 
    478         pw.println("Ringing calls:");
    479         pw.increaseIndent();
    480         dumpCallsInCollection(pw, mRingingCalls);
    481         pw.decreaseIndent();
    482 
    483         pw.println("Holding calls:");
    484         pw.increaseIndent();
    485         dumpCallsInCollection(pw, mHoldingCalls);
    486         pw.decreaseIndent();
    487 
    488         pw.println("Foreground call:");
    489         pw.println(mForegroundCall);
    490 
    491         pw.println("CallAudioModeStateMachine pending messages:");
    492         pw.increaseIndent();
    493         mCallAudioModeStateMachine.dumpPendingMessages(pw);
    494         pw.decreaseIndent();
    495 
    496         pw.println("CallAudioRouteStateMachine pending messages:");
    497         pw.increaseIndent();
    498         mCallAudioRouteStateMachine.dumpPendingMessages(pw);
    499         pw.decreaseIndent();
    500     }
    501 
    502     @VisibleForTesting
    503     public void setIsTonePlaying(boolean isTonePlaying) {
    504         mIsTonePlaying = isTonePlaying;
    505         mCallAudioModeStateMachine.sendMessageWithArgs(
    506                 isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING
    507                         : CallAudioModeStateMachine.TONE_STOPPED_PLAYING,
    508                 makeArgsForModeStateMachine());
    509     }
    510 
    511     private void onCallLeavingState(Call call, int state) {
    512         switch (state) {
    513             case CallState.ACTIVE:
    514             case CallState.CONNECTING:
    515                 onCallLeavingActiveDialingOrConnecting();
    516                 break;
    517             case CallState.RINGING:
    518                 onCallLeavingRinging();
    519                 break;
    520             case CallState.ON_HOLD:
    521                 onCallLeavingHold();
    522                 break;
    523             case CallState.PULLING:
    524                 onCallLeavingActiveDialingOrConnecting();
    525                 break;
    526             case CallState.DIALING:
    527                 stopRingbackForCall(call);
    528                 onCallLeavingActiveDialingOrConnecting();
    529                 break;
    530         }
    531     }
    532 
    533     private void onCallEnteringState(Call call, int state) {
    534         switch (state) {
    535             case CallState.ACTIVE:
    536             case CallState.CONNECTING:
    537                 onCallEnteringActiveDialingOrConnecting();
    538                 break;
    539             case CallState.RINGING:
    540                 onCallEnteringRinging();
    541                 break;
    542             case CallState.ON_HOLD:
    543                 onCallEnteringHold();
    544                 break;
    545             case CallState.PULLING:
    546                 onCallEnteringActiveDialingOrConnecting();
    547                 break;
    548             case CallState.DIALING:
    549                 onCallEnteringActiveDialingOrConnecting();
    550                 playRingbackForCall(call);
    551                 break;
    552         }
    553     }
    554 
    555     private void onCallLeavingActiveDialingOrConnecting() {
    556         if (mActiveDialingOrConnectingCalls.size() == 0) {
    557             mCallAudioModeStateMachine.sendMessageWithArgs(
    558                     CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS,
    559                     makeArgsForModeStateMachine());
    560         }
    561     }
    562 
    563     private void onCallLeavingRinging() {
    564         if (mRingingCalls.size() == 0) {
    565             mCallAudioModeStateMachine.sendMessageWithArgs(
    566                     CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
    567                     makeArgsForModeStateMachine());
    568         }
    569     }
    570 
    571     private void onCallLeavingHold() {
    572         if (mHoldingCalls.size() == 0) {
    573             mCallAudioModeStateMachine.sendMessageWithArgs(
    574                     CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS,
    575                     makeArgsForModeStateMachine());
    576         }
    577     }
    578 
    579     private void onCallEnteringActiveDialingOrConnecting() {
    580         if (mActiveDialingOrConnectingCalls.size() == 1) {
    581             mCallAudioModeStateMachine.sendMessageWithArgs(
    582                     CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL,
    583                     makeArgsForModeStateMachine());
    584         }
    585     }
    586 
    587     private void onCallEnteringRinging() {
    588         if (mRingingCalls.size() == 1) {
    589             mCallAudioModeStateMachine.sendMessageWithArgs(
    590                     CallAudioModeStateMachine.NEW_RINGING_CALL,
    591                     makeArgsForModeStateMachine());
    592         }
    593     }
    594 
    595     private void onCallEnteringHold() {
    596         if (mHoldingCalls.size() == 1) {
    597             mCallAudioModeStateMachine.sendMessageWithArgs(
    598                     CallAudioModeStateMachine.NEW_HOLDING_CALL,
    599                     makeArgsForModeStateMachine());
    600         }
    601     }
    602 
    603     private void updateForegroundCall() {
    604         Call oldForegroundCall = mForegroundCall;
    605         if (mActiveDialingOrConnectingCalls.size() > 0) {
    606             // Give preference for connecting calls over active/dialing for foreground-ness.
    607             Call possibleConnectingCall = null;
    608             for (Call call : mActiveDialingOrConnectingCalls) {
    609                 if (call.getState() == CallState.CONNECTING) {
    610                     possibleConnectingCall = call;
    611                 }
    612             }
    613             mForegroundCall = possibleConnectingCall == null ?
    614                     mActiveDialingOrConnectingCalls.iterator().next() : possibleConnectingCall;
    615         } else if (mRingingCalls.size() > 0) {
    616             mForegroundCall = mRingingCalls.iterator().next();
    617         } else if (mHoldingCalls.size() > 0) {
    618             mForegroundCall = mHoldingCalls.iterator().next();
    619         } else {
    620             mForegroundCall = null;
    621         }
    622 
    623         if (mForegroundCall != oldForegroundCall) {
    624             mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
    625                     CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
    626             mDtmfLocalTonePlayer.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
    627             maybePlayHoldTone();
    628         }
    629     }
    630 
    631     @NonNull
    632     private CallAudioModeStateMachine.MessageArgs makeArgsForModeStateMachine() {
    633         return new CallAudioModeStateMachine.MessageArgs(
    634                 mActiveDialingOrConnectingCalls.size() > 0,
    635                 mRingingCalls.size() > 0,
    636                 mHoldingCalls.size() > 0,
    637                 mIsTonePlaying,
    638                 mForegroundCall != null && mForegroundCall.getIsVoipAudioMode(),
    639                 Log.createSubsession());
    640     }
    641 
    642     private void playToneForDisconnectedCall(Call call) {
    643         // If this call is being disconnected as a result of being handed over to another call,
    644         // we will not play a disconnect tone.
    645         if (call.isHandoverInProgress()) {
    646             Log.i(LOG_TAG, "Omitting tone because %s is being handed over.", call);
    647             return;
    648         }
    649 
    650         if (mForegroundCall != null && call != mForegroundCall && mCalls.size() > 1) {
    651             Log.v(LOG_TAG, "Omitting tone because we are not foreground" +
    652                     " and there is another call.");
    653             return;
    654         }
    655 
    656         if (call.getDisconnectCause() != null) {
    657             int toneToPlay = InCallTonePlayer.TONE_INVALID;
    658 
    659             Log.v(this, "Disconnect cause: %s.", call.getDisconnectCause());
    660 
    661             switch(call.getDisconnectCause().getTone()) {
    662                 case ToneGenerator.TONE_SUP_BUSY:
    663                     toneToPlay = InCallTonePlayer.TONE_BUSY;
    664                     break;
    665                 case ToneGenerator.TONE_SUP_CONGESTION:
    666                     toneToPlay = InCallTonePlayer.TONE_CONGESTION;
    667                     break;
    668                 case ToneGenerator.TONE_CDMA_REORDER:
    669                     toneToPlay = InCallTonePlayer.TONE_REORDER;
    670                     break;
    671                 case ToneGenerator.TONE_CDMA_ABBR_INTERCEPT:
    672                     toneToPlay = InCallTonePlayer.TONE_INTERCEPT;
    673                     break;
    674                 case ToneGenerator.TONE_CDMA_CALLDROP_LITE:
    675                     toneToPlay = InCallTonePlayer.TONE_CDMA_DROP;
    676                     break;
    677                 case ToneGenerator.TONE_SUP_ERROR:
    678                     toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER;
    679                     break;
    680                 case ToneGenerator.TONE_PROP_PROMPT:
    681                     toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
    682                     break;
    683             }
    684 
    685             Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay);
    686 
    687             if (toneToPlay != InCallTonePlayer.TONE_INVALID) {
    688                 mPlayerFactory.createPlayer(toneToPlay).startTone();
    689             }
    690         }
    691     }
    692 
    693     private void playRingbackForCall(Call call) {
    694         if (call == mForegroundCall && call.isRingbackRequested()) {
    695             mRingbackPlayer.startRingbackForCall(call);
    696         }
    697     }
    698 
    699     private void stopRingbackForCall(Call call) {
    700         mRingbackPlayer.stopRingbackForCall(call);
    701     }
    702 
    703     /**
    704      * Determines if a hold tone should be played and then starts or stops it accordingly.
    705      */
    706     private void maybePlayHoldTone() {
    707         if (shouldPlayHoldTone()) {
    708             if (mHoldTonePlayer == null) {
    709                 mHoldTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
    710                 mHoldTonePlayer.startTone();
    711             }
    712         } else {
    713             if (mHoldTonePlayer != null) {
    714                 mHoldTonePlayer.stopTone();
    715                 mHoldTonePlayer = null;
    716             }
    717         }
    718     }
    719 
    720     /**
    721      * Determines if a hold tone should be played.
    722      * A hold tone should be played only if foreground call is equals with call which is
    723      * remotely held.
    724      *
    725      * @return {@code true} if the the hold tone should be played, {@code false} otherwise.
    726      */
    727     private boolean shouldPlayHoldTone() {
    728         Call foregroundCall = getForegroundCall();
    729         // If there is no foreground call, no hold tone should play.
    730         if (foregroundCall == null) {
    731             return false;
    732         }
    733 
    734         // If another call is ringing, no hold tone should play.
    735         if (mCallsManager.hasRingingCall()) {
    736             return false;
    737         }
    738 
    739         // If the foreground call isn't active, no hold tone should play. This might happen, for
    740         // example, if the user puts a remotely held call on hold itself.
    741         if (!foregroundCall.isActive()) {
    742             return false;
    743         }
    744 
    745         return foregroundCall.isRemotelyHeld();
    746     }
    747 
    748     private void dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls) {
    749         for (Call call : calls) {
    750             if (call != null) pw.println(call.getId());
    751         }
    752     }
    753 
    754     private void maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(Call call) {
    755         // Check to see if the call being answered/rejected is the only ringing call, since this
    756         // will be called before the connection service acknowledges the state change.
    757         if (mRingingCalls.size() == 0 ||
    758                 (mRingingCalls.size() == 1 && call == mRingingCalls.iterator().next())) {
    759             mRinger.stopRinging();
    760             mRinger.stopCallWaiting();
    761         }
    762     }
    763 
    764     private boolean shouldPlayDisconnectTone(int oldState, int newState) {
    765         if (newState != CallState.DISCONNECTED) {
    766             return false;
    767         }
    768         return oldState == CallState.ACTIVE ||
    769                 oldState == CallState.DIALING ||
    770                 oldState == CallState.ON_HOLD;
    771     }
    772 
    773     @VisibleForTesting
    774     public Set<Call> getTrackedCalls() {
    775         return mCalls;
    776     }
    777 
    778     @VisibleForTesting
    779     public SparseArray<LinkedHashSet<Call>> getCallStateToCalls() {
    780         return mCallStateToCalls;
    781     }
    782 }