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

Commit 257676d1 authored by Grant Menke's avatar Grant Menke
Browse files

Dont flag disconnected calls that are created but never added.

Calls that are intentionally disconnected by Telecom before they are added to CallsManager are working as intended and should not trigger anomaly reports. This CL stops the CallANomalyWatchdog from tracking these calls. This CL also ensures CallsManager notifies the watchdog appropriately when a call of this type is marked as disconnected.

Bug: 269467189
Test: atest CallAnomalyWatchdogTest && atest CallsManagerTest
Change-Id: Id47166fc5031221cd9686d133e7cc9516cc3f03a
parent 1b977579
Loading
Loading
Loading
Loading
+43 −16
Original line number Diff line number Diff line
@@ -164,24 +164,27 @@ public class CallAnomalyWatchdog extends CallsManagerListenerBase implements Cal
        maybeTrackCall(call);
    }

    /**
     * Override of {@link CallsManagerListenerBase} to track when calls are created but
     * intentionally not added to mCalls. These calls should no longer be tracked by the
     * CallAnomalyWatchdog.
     * @param call the call
     */

    @Override
    public void onCallCreatedButNeverAdded(Call call) {
        Log.i(this, "onCallCreatedButNeverAdded: call=%s", call.toString());
        stopTrackingCall(call);
    }

    /**
     * Override of {@link CallsManagerListenerBase} to track when calls are removed
     * @param call the call
     */
    @Override
    public void onCallRemoved(Call call) {
        if (mScheduledFutureMap.containsKey(call)) {
            ScheduledFuture<?> existingTimeout = mScheduledFutureMap.get(call);
            existingTimeout.cancel(false /* cancelIfRunning */);
            mScheduledFutureMap.remove(call);
        }
        if (mCallsPendingDestruction.contains(call)) {
            mCallsPendingDestruction.remove(call);
        }
        if (mWatchdogCallStateMap.containsKey(call)) {
            mWatchdogCallStateMap.remove(call);
        }
        call.removeListener(this);
        Log.i(this, "onCallRemoved: call=%s", call.toString());
        stopTrackingCall(call);
    }

    /**
@@ -192,7 +195,10 @@ public class CallAnomalyWatchdog extends CallsManagerListenerBase implements Cal
     * @param newState the new state
     */
    @Override
    public void onCallStateChanged(Call call, int oldState, int newState) { maybeTrackCall(call); }
    public void onCallStateChanged(Call call, int oldState, int newState) {
        Log.i(this, "onCallStateChanged: call=%s", call.toString());
        maybeTrackCall(call);
    }

    /**
     * Override of {@link Call.Listener} so we can capture successful creation of calls.
@@ -211,7 +217,8 @@ public class CallAnomalyWatchdog extends CallsManagerListenerBase implements Cal
     */
    @Override
    public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {
        maybeTrackCall(call);
        Log.i(this, "onFailedOutgoingCall: call=%s", call.toString());
        stopTrackingCall(call);
    }

    /**
@@ -229,7 +236,27 @@ public class CallAnomalyWatchdog extends CallsManagerListenerBase implements Cal
     */
    @Override
    public void onFailedIncomingCall(Call call) {
        maybeTrackCall(call);
        Log.i(this, "onFailedIncomingCall: call=%s", call.toString());
        stopTrackingCall(call);
    }

    /**
     * Helper method used to stop CallAnomalyWatchdog from tracking or destroying the call.
     * @param call the call.
     */
    private void stopTrackingCall(Call call) {
        if (mScheduledFutureMap.containsKey(call)) {
            ScheduledFuture<?> existingTimeout = mScheduledFutureMap.get(call);
            existingTimeout.cancel(false /* cancelIfRunning */);
            mScheduledFutureMap.remove(call);
        }
        if (mCallsPendingDestruction.contains(call)) {
            mCallsPendingDestruction.remove(call);
        }
        if (mWatchdogCallStateMap.containsKey(call)) {
            mWatchdogCallStateMap.remove(call);
        }
        call.removeListener(this);
    }

    /**
@@ -273,7 +300,7 @@ public class CallAnomalyWatchdog extends CallsManagerListenerBase implements Cal
        }
    }

    private long getTimeoutMillis(Call call, WatchdogCallState state) {
    public long getTimeoutMillis(Call call, WatchdogCallState state) {
        boolean isVoip = call.getIsVoipAudioMode();
        boolean isEmergency = call.isEmergencyCall();

+25 −0
Original line number Diff line number Diff line
@@ -182,6 +182,7 @@ public class CallsManager extends Call.ListenerBase
         */
        default void onCallCreated(Call call) {}
        void onCallAdded(Call call);
        void onCallCreatedButNeverAdded(Call call);
        void onCallRemoved(Call call);
        void onCallStateChanged(Call call, int oldState, int newState);
        void onConnectionServiceChanged(
@@ -279,6 +280,11 @@ public class CallsManager extends Call.ListenerBase
    public static final String EXCEPTION_RETRIEVING_PHONE_ACCOUNTS_EMERGENCY_ERROR_MSG =
            "Exception thrown while retrieving list of potential phone accounts when placing an "
                    + "emergency call.";
    public static final UUID EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_UUID =
            UUID.fromString("f9a916c8-8d61-4550-9ad3-11c2e84f6364");
    public static final String EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_MSG =
            "An emergency call was disconnected after the connection was created but before the "
                    + "call was successfully added to CallsManager.";

    private static final int[] OUTGOING_CALL_STATES =
            {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
@@ -3498,6 +3504,8 @@ public class CallsManager extends Call.ListenerBase
     * @param disconnectCause The disconnect cause, see {@link android.telecom.DisconnectCause}.
     */
    public void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) {
        Log.i(this, "markCallAsDisconnected: call=%s; disconnectCause=%s",
                call.toString(), disconnectCause.toString());
        int oldState = call.getState();
        if (call.getState() == CallState.SIMULATED_RINGING
                && disconnectCause.getCode() == DisconnectCause.REMOTE) {
@@ -3522,6 +3530,17 @@ public class CallsManager extends Call.ListenerBase
            }
        }

        // Notify listeners that the call was disconnected before being added to CallsManager.
        // Listeners will not receive onAdded or onRemoved callbacks.
        if (!mCalls.contains(call)) {
            if (call.isEmergencyCall()) {
                mAnomalyReporter.reportAnomaly(
                        EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_UUID,
                        EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_MSG);
            }
            mListeners.forEach(l -> l.onCallCreatedButNeverAdded(call));
        }

        // If a call diagnostic service is in use, we will log the original telephony-provided
        // disconnect cause, inform the CDS of the disconnection, and then chain the update of the
        // call state until AFTER the CDS reports it's result back.
@@ -5475,6 +5494,9 @@ public class CallsManager extends Call.ListenerBase
        } else {
            call.setConnectionService(service);
            service.createConnectionFailed(call);
            if (!mCalls.contains(call)){
                mListeners.forEach(l -> l.onCallCreatedButNeverAdded(call));
            }
        }
    }

@@ -5497,6 +5519,9 @@ public class CallsManager extends Call.ListenerBase
        } else {
            call.setConnectionService(service);
            service.createConferenceFailed(call);
            if (!mCalls.contains(call)){
                mListeners.forEach(l -> l.onCallCreatedButNeverAdded(call));
            }
        }
    }

+4 −0
Original line number Diff line number Diff line
@@ -34,6 +34,10 @@ public abstract class CallsManagerListenerBase implements CallsManager.CallsMana
    public void onCallRemoved(Call call) {
    }

    @Override
    public void onCallCreatedButNeverAdded(Call call) {
    }

    @Override
    public void onCallStateChanged(Call call, int oldState, int newState) {
    }
+86 −0
Original line number Diff line number Diff line
@@ -21,11 +21,13 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.ComponentName;
import android.net.Uri;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;

@@ -754,6 +756,90 @@ public class CallAnomalyWatchdogTest extends TelecomTestCase {
                advanceTime(TEST_NON_VOIP_EMERGENCY_TRANSITORY_MILLIS + 1);
    }

    /**
     * Emulate the case where a new incoming call is created but the connection fails for a known
     * reason before being added to CallsManager. In this case, the watchdog should stop tracking
     * the call and not trigger an anomaly report.
     */
    @Test
    public void testIncomingCallCreatedButNotAddedNoAnomalyReport() {
        //The call is created:
        Call call = getCall();
        call.setState(CallState.NEW, "foo");
        call.setIsCreateConnectionComplete(false);
        mCallAnomalyWatchdog.onCallCreated(call);

        //The connection fails before being added to CallsManager for a known reason:
        call.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));

        // Move the clock forward:
        when(mMockClockProxy.elapsedRealtime()).
                thenReturn(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
        mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);

        //Ensure an anomaly report is not generated:
        verify(mAnomalyReporterAdapter, never()).reportAnomaly(
                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_MSG);
    }

    /**
     * Emulate the case where a new outgoing call is created but the connection fails for a known
     * reason before being added to CallsManager. In this case, the watchdog should stop tracking
     * the call and not trigger an anomaly report.
     */
    @Test
    public void testOutgoingCallCreatedButNotAddedNoAnomalyReport() {
        //The call is created:
        Call call = getCall();
        call.setCallDirection(Call.CALL_DIRECTION_OUTGOING);
        call.setState(CallState.NEW, "foo");
        call.setIsCreateConnectionComplete(false);
        mCallAnomalyWatchdog.onCallCreated(call);

        //The connection fails before being added to CallsManager for a known reason.
        call.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));

        // Move the clock forward:
        when(mMockClockProxy.elapsedRealtime()).
                thenReturn(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
        mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);

        //Ensure an anomaly report is not generated:
        verify(mAnomalyReporterAdapter, never()).reportAnomaly(
                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_MSG);
    }

    /**
     * Emulate the case where a new incoming call is created but the connection fails for a known
     * reason before being added to CallsManager and CallsManager notifies the watchdog by invoking
     * onCallCreatedButNeverAdded(). In this case, the watchdog should stop tracking
     * the call and not trigger an anomaly report.
     */
    @Test
    public void testCallCreatedButNotAddedPreventsAnomalyReport() {
        //The call is created:
        Call call = getCall();
        call.setState(CallState.NEW, "foo");
        call.setIsCreateConnectionComplete(false);
        mCallAnomalyWatchdog.onCallCreated(call);

        //Telecom cancels the connection before adding it to CallsManager:
        mCallAnomalyWatchdog.onCallCreatedButNeverAdded(call);

        // Move the clock forward:
        when(mMockClockProxy.elapsedRealtime()).
                thenReturn(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
        mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);

        //Ensure an anomaly report is not generated:
        verify(mAnomalyReporterAdapter, never()).reportAnomaly(
                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_MSG);
    }


    /**
     * @return an instance of {@link Call} for testing purposes.
     */
+42 −0
Original line number Diff line number Diff line
@@ -2000,6 +2000,48 @@ public class CallsManagerTest extends TelecomTestCase {
                SELF_MANAGED_HANDLE.getUserHandle()));
    }

    /**
     * Emulate the case where a new incoming call is created but the connection fails for a known
     * reason before being added to CallsManager. In this case, the listeners should be notified
     * properly.
     */
    @Test
    public void testIncomingCallCreatedButNotAddedNotifyListener() {
        //The call is created and a listener is added:
        Call incomingCall = createCall(SIM_2_HANDLE, null, CallState.NEW);
        CallsManager.CallsManagerListener listener = mock(CallsManager.CallsManagerListener.class);
        mCallsManager.addListener(listener);

        //The connection fails before being added to CallsManager for a known reason:
        incomingCall.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));

        //Ensure the listener is notified properly:
        verify(listener).onCallCreatedButNeverAdded(incomingCall);
    }

    /**
     * Emulate the case where a new incoming call is created but the connection fails for a known
     * reason after being added to CallsManager. Since the call was added to CallsManager, the
     * listeners should not be notified via onCallCreatedButNeverAdded().
     */
    @Test
    public void testIncomingCallCreatedAndAddedDoNotNotifyListener() {
        //The call is created and a listener is added:
        Call incomingCall = createCall(SIM_2_HANDLE, null, CallState.NEW);
        CallsManager.CallsManagerListener listener = mock(CallsManager.CallsManagerListener.class);
        mCallsManager.addListener(listener);

        //The call is added to CallsManager:
        mCallsManager.addCall(incomingCall);

        //The connection fails after being added to CallsManager for a known reason:
        incomingCall.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));

        //Since the call was added to CallsManager, onCallCreatedButNeverAdded shouldn't be invoked:
        verify(listener, never()).onCallCreatedButNeverAdded(incomingCall);
    }


    @Test
    public void testIsInSelfManagedCallOnlySelfManaged() {
        Call selfManagedCall = createCall(SELF_MANAGED_HANDLE, CallState.ACTIVE);