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