Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2d889159 authored by Tyler Gunn's avatar Tyler Gunn
Browse files

Ensure audio mode is updated when the foreground call changes.

In the past, the audio mode (voip/modem) was triggered in a few cases
where new calls started or calls swap, as a side effect of notifying the
CallAudioRoute/ModeStateMachines of those operations.

There are two cases where that was not enough:
1) When an ongoing unholdable pstn call is disconnected due to a voip
call being answered.
2) When swapping between an ongoing PSTN call and VOIP call in some cases
the new foreground call would not correctly be set -- this caused the fg
call to be tracked incorrectly and the audio mode not to be set.

To correct this, updated the logic to determine which call is the
foreground call so that it will preferentially use an active or dialing
call which is not locally disconnecting (ie this is the original bug case)
to become the foreground call.  Failing that it'll fall back to the old
behavior of choosing the first call.

Finally, to make audio mode changing more reliable, added code which is
executed on change of foreground call.  We will now send a
FOREGROUND_VOIP_MODE_CHANGE event to the CallAudioModeStateMachine so that
it can properly transition between voip/modem audio modes.  This signal
does overlap with some of the other cases (new call added for example),
however the net effect is no audio mode state transition would happen in
those cases.

Test: Manual test for repro steps mentioned in bug.
Test: Added new unit test coverage for this case in CallAudioManagerTest
Fixes: 289861657
Change-Id: Ie337985f8dea9ca688af4f6a08a5a9dc9a92943b
parent 9d857d2c
Loading
Loading
Loading
Loading
+34 −4
Original line number Diff line number Diff line
@@ -36,6 +36,8 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.LinkedHashSet;
import java.util.stream.Collectors;


public class CallAudioManager extends CallsManagerListenerBase {

@@ -116,7 +118,7 @@ public class CallAudioManager extends CallsManagerListenerBase {
            // State did not change, so no need to do anything.
            return;
        }
        Log.d(LOG_TAG, "Call state changed for TC@%s: %s -> %s", call.getId(),
        Log.i(this, "onCallStateChanged: Call state changed for TC@%s: %s -> %s", call.getId(),
                CallState.toString(oldState), CallState.toString(newState));

        removeCallFromAllBins(call);
@@ -761,6 +763,7 @@ public class CallAudioManager extends CallsManagerListenerBase {

    private void updateForegroundCall() {
        Call oldForegroundCall = mForegroundCall;

        if (mActiveDialingOrConnectingCalls.size() > 0) {
            // Give preference for connecting calls over active/dialing for foreground-ness.
            Call possibleConnectingCall = null;
@@ -769,8 +772,21 @@ public class CallAudioManager extends CallsManagerListenerBase {
                    possibleConnectingCall = call;
                }
            }
            mForegroundCall = possibleConnectingCall == null ?
                    mActiveDialingOrConnectingCalls.iterator().next() : possibleConnectingCall;
            // Prefer a connecting call
            if (possibleConnectingCall != null) {
                mForegroundCall = possibleConnectingCall;
            } else {
                // Next, prefer an active or dialing call which is not in the process of being
                // disconnected.
                mForegroundCall = mActiveDialingOrConnectingCalls
                        .stream()
                        .filter(c -> (c.getState() == CallState.ACTIVE
                                || c.getState() == CallState.DIALING)
                                && !c.isLocallyDisconnecting())
                        .findFirst()
                        // If we can't find one, then just fall back to the first one.
                        .orElse(mActiveDialingOrConnectingCalls.iterator().next());
            }
        } else if (mRingingCalls.size() > 0) {
            mForegroundCall = mRingingCalls.iterator().next();
        } else if (mHoldingCalls.size() > 0) {
@@ -778,10 +794,24 @@ public class CallAudioManager extends CallsManagerListenerBase {
        } else {
            mForegroundCall = null;
        }

        Log.i(this, "updateForegroundCall; oldFg=%s, newFg=%s, aDC=%s, ring=%s, hold=%s",
                (oldForegroundCall == null ? "none" : oldForegroundCall.getId()),
                (mForegroundCall == null ? "none" : mForegroundCall.getId()),
                mActiveDialingOrConnectingCalls.stream().map(c -> c.getId()).collect(
                        Collectors.joining(",")),
                mRingingCalls.stream().map(c -> c.getId()).collect(Collectors.joining(",")),
                mHoldingCalls.stream().map(c -> c.getId()).collect(Collectors.joining(","))
        );
        if (mForegroundCall != oldForegroundCall) {
            mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
                    CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);

            if (mForegroundCall != null) {
                // Ensure the voip audio mode for the new foreground call is taken into account.
                mCallAudioModeStateMachine.sendMessageWithArgs(
                        CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE,
                        makeArgsForModeStateMachine());
            }
            mDtmfLocalTonePlayer.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
            maybePlayHoldTone();
        }
+73 −3
Original line number Diff line number Diff line
@@ -59,6 +59,7 @@ import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -277,7 +278,8 @@ public class CallAudioManagerTest extends TelecomTestCase {
        verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
                eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
        assertMessageArgEquality(expectedArgs, captor.getValue());
        verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
        // Expet another invocation due to audio mode change signal.
        verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
                anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));


@@ -286,7 +288,7 @@ public class CallAudioManagerTest extends TelecomTestCase {
        verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
                eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
        assertMessageArgEquality(expectedArgs, captor.getValue());
        verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
        verify(mCallAudioModeStateMachine, times(4)).sendMessageWithArgs(
                anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));

        disconnectCall(call);
@@ -327,7 +329,8 @@ public class CallAudioManagerTest extends TelecomTestCase {
        verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
                eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
        assertMessageArgEquality(expectedArgs, captor.getValue());
        verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
        // Expect an extra time due to audio mode change signal
        verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
                anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));

        // Ensure we started ringback.
@@ -702,6 +705,73 @@ public class CallAudioManagerTest extends TelecomTestCase {
        assertFalse(captor.getValue().isStreaming);
    }

    @SmallTest
    @Test
    public void testTriggerAudioManagerModeChange() {
        // Start with an incoming PSTN call
        Call pstnCall = mock(Call.class);
        when(pstnCall.getState()).thenReturn(CallState.RINGING);
        when(pstnCall.getIsVoipAudioMode()).thenReturn(false);
        ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor = makeNewCaptor();

        // Add the call
        mCallAudioManager.onCallAdded(pstnCall);
        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
                eq(CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE), captor.capture());
        CallAudioModeStateMachine.MessageArgs expectedArgs =
                new Builder()
                        .setHasActiveOrDialingCalls(false)
                        .setHasRingingCalls(true)
                        .setHasHoldingCalls(false)
                        .setIsTonePlaying(false)
                        .setHasAudioProcessingCalls(false)
                        .setForegroundCallIsVoip(false)
                        .setSession(null)
                        .setForegroundCallIsVoip(false)
                        .build();
        assertMessageArgEquality(expectedArgs, captor.getValue());
        clearInvocations(mCallAudioModeStateMachine); // Avoid verifying for previous calls

        // Make call active; don't expect there to be an audio mode transition.
        when(pstnCall.getState()).thenReturn(CallState.ACTIVE);
        mCallAudioManager.onCallStateChanged(pstnCall, CallState.RINGING, CallState.ACTIVE);
        verify(mCallAudioModeStateMachine, never()).sendMessageWithArgs(
                eq(CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE),
                any(CallAudioModeStateMachine.MessageArgs.class));
        clearInvocations(mCallAudioModeStateMachine); // Avoid verifying for previous calls

        // Add a new Voip call in ringing state; this should not result in a direct audio mode
        // change.
        Call voipCall = mock(Call.class);
        when(voipCall.getState()).thenReturn(CallState.RINGING);
        when(voipCall.getIsVoipAudioMode()).thenReturn(true);
        mCallAudioManager.onCallAdded(voipCall);
        verify(mCallAudioModeStateMachine, never()).sendMessageWithArgs(
                eq(CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE),
                any(CallAudioModeStateMachine.MessageArgs.class));
        clearInvocations(mCallAudioModeStateMachine); // Avoid verifying for previous calls

        // Make voip call active and set the PSTN call to locally disconnecting; the new foreground
        // call will be the voip call.
        when(pstnCall.isLocallyDisconnecting()).thenReturn(true);
        when(voipCall.getState()).thenReturn(CallState.ACTIVE);
        mCallAudioManager.onCallStateChanged(voipCall, CallState.RINGING, CallState.ACTIVE);
        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
                eq(CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE), captor.capture());
        CallAudioModeStateMachine.MessageArgs expectedArgs2 =
                new Builder()
                        .setHasActiveOrDialingCalls(true)
                        .setHasRingingCalls(false)
                        .setHasHoldingCalls(false)
                        .setIsTonePlaying(false)
                        .setHasAudioProcessingCalls(false)
                        .setForegroundCallIsVoip(false)
                        .setSession(null)
                        .setForegroundCallIsVoip(true)
                        .build();
        assertMessageArgEquality(expectedArgs2, captor.getValue());
    }

    private Call createSimulatedRingingCall() {
        Call call = mock(Call.class);
        when(call.getState()).thenReturn(CallState.SIMULATED_RINGING);