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

Commit c98cb09d authored by Thomas Stuart's avatar Thomas Stuart
Browse files

disconnect self-managed calls stuck in starting state

VoIP calls that do not move out of the NEW, CONNECTING,
DIALING, or RINGING state after the TIMEOUT  will now be
disconnected.  This prevents stuck calls in the system
which can have affects on UI (e.g. lock screen stays on).

This change was manually verified by creating calls in
the CONNECTING and RINGING states and ensuring they
are disconnected and destroyed if they are not moved
out of the state before the timeout.

Bug:  360298368
Flag: com.android.server.telecom.flags.disconnect_self_managed_stuck_startup_calls
Test: manual
Change-Id: I1aeb99bb5b81df600d9aa1bac253ca8ce65f4f0c
parent 7454e970
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -8,3 +8,11 @@ flag {
  description: "When getCurrentFocusCall times out, generate an anom. report"
  bug: "309541253"
}

# OWNER=tjstuart TARGET=25Q2
flag {
  name: "disconnect_self_managed_stuck_startup_calls"
  namespace: "telecom"
  description: "If a self-managed call is stuck in certain states, disconnect it"
  bug: "360298368"
}
+72 −3
Original line number Diff line number Diff line
@@ -18,15 +18,22 @@ package com.android.server.telecom;

import static com.android.server.telecom.LogUtils.Events.STATE_TIMEOUT;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.telecom.ConnectionService;
import android.telecom.DisconnectCause;
import android.telecom.Log;
import android.telecom.PhoneAccountHandle;
import android.util.LocalLog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.telecom.stats.CallStateChangedAtomWriter;
import com.android.server.telecom.flags.FeatureFlags;

import java.util.Collections;
import java.util.Map;
@@ -113,6 +120,7 @@ public class CallAnomalyWatchdog extends CallsManagerListenerBase implements Cal
    private final TelecomSystem.SyncRoot mLock;
    private final Timeouts.Adapter mTimeoutAdapter;
    private final ClockProxy mClockProxy;
    private final FeatureFlags mFeatureFlags;
    private AnomalyReporterAdapter mAnomalyReporter = new AnomalyReporterAdapterImpl();
    // Pre-allocate space for 2 calls; realistically thats all we should ever need (tm)
    private final Map<Call, ScheduledFuture<?>> mScheduledFutureMap = new ConcurrentHashMap<>(2);
@@ -140,6 +148,11 @@ public class CallAnomalyWatchdog extends CallsManagerListenerBase implements Cal
            UUID.fromString("d57d8aab-d723-485e-a0dd-d1abb0f346c8");
    public static final String WATCHDOG_DISCONNECTED_STUCK_EMERGENCY_CALL_MSG =
            "Telecom CallAnomalyWatchdog caught and disconnected a stuck/zombie emergency call.";
    public static final UUID WATCHDOG_DISCONNECTED_STUCK_VOIP_CALL_UUID =
            UUID.fromString("3fbecd12-059d-4fd3-87b7-6c3079891c23");
    public static final String WATCHDOG_DISCONNECTED_STUCK_VOIP_CALL_MSG =
            "Telecom CallAnomalyWatchdog caught stuck VoIP call in a starting state";


    @VisibleForTesting
    public void setAnomalyReporterAdapter(AnomalyReporterAdapter mAnomalyReporterAdapter){
@@ -148,10 +161,12 @@ public class CallAnomalyWatchdog extends CallsManagerListenerBase implements Cal

    public CallAnomalyWatchdog(ScheduledExecutorService executorService,
            TelecomSystem.SyncRoot lock,
            FeatureFlags featureFlags,
            Timeouts.Adapter timeoutAdapter, ClockProxy clockProxy,
            EmergencyCallDiagnosticLogger emergencyCallDiagnosticLogger) {
        mScheduledExecutorService = executorService;
        mLock = lock;
        mFeatureFlags = featureFlags;
        mTimeoutAdapter = timeoutAdapter;
        mClockProxy = clockProxy;
        mEmergencyCallDiagnosticLogger = emergencyCallDiagnosticLogger;
@@ -272,8 +287,13 @@ public class CallAnomalyWatchdog extends CallsManagerListenerBase implements Cal
     */
    private void maybeTrackCall(Call call) {
        final WatchdogCallState currentState = mWatchdogCallStateMap.get(call);
        boolean isCreateConnectionComplete = call.isCreateConnectionComplete();
        if (mFeatureFlags.disconnectSelfManagedStuckStartupCalls()) {
            isCreateConnectionComplete =
                    isCreateConnectionComplete || call.isTransactionalCall();
        }
        final WatchdogCallState newState = new WatchdogCallState(call.getState(),
                call.isCreateConnectionComplete(), mClockProxy.elapsedRealtime());
                isCreateConnectionComplete, mClockProxy.elapsedRealtime());
        if (Objects.equals(currentState, newState)) {
            // No state change; skip.
            return;
@@ -348,8 +368,13 @@ public class CallAnomalyWatchdog extends CallsManagerListenerBase implements Cal
                }
                // Ensure that at timeout we are still in the original state when we posted the
                // timeout.
                boolean isCreateConnectionComplete = call.isCreateConnectionComplete();
                if (mFeatureFlags.disconnectSelfManagedStuckStartupCalls()) {
                    isCreateConnectionComplete =
                            isCreateConnectionComplete || call.isTransactionalCall();
                }
                final WatchdogCallState expiredState = new WatchdogCallState(call.getState(),
                        call.isCreateConnectionComplete(), mClockProxy.elapsedRealtime());
                        isCreateConnectionComplete, mClockProxy.elapsedRealtime());
                if (expiredState.equals(newState)
                        && getDurationInCurrentStateMillis(newState) > timeoutMillis) {
                    // The call has been in this transitory or intermediate state too long,
@@ -368,7 +393,7 @@ public class CallAnomalyWatchdog extends CallsManagerListenerBase implements Cal
                                WATCHDOG_DISCONNECTED_STUCK_CALL_MSG);
                    }

                    if (isEnabledDisconnect) {
                    if (isEnabledDisconnect || isInSelfManagedStuckStartingState(call)) {
                        call.setOverrideDisconnectCauseCode(
                                new DisconnectCause(DisconnectCause.ERROR, "state_timeout"));
                        call.disconnect("State timeout");
@@ -387,6 +412,50 @@ public class CallAnomalyWatchdog extends CallsManagerListenerBase implements Cal
        return cleanupRunnable;
    }

    private boolean isInSelfManagedStuckStartingState(Call call) {
        Context context = call.getContext();
        if (!mFeatureFlags.disconnectSelfManagedStuckStartupCalls() || context == null) {
            return false;
        }
        int currentStuckState = call.getState();
        return call.isSelfManaged() &&
                (currentStuckState == CallState.NEW ||
                        currentStuckState == CallState.RINGING ||
                        currentStuckState == CallState.DIALING ||
                        currentStuckState == CallState.CONNECTING) &&
                isVanillaIceCreamBuildOrHigher(context, call);
    }

    private boolean isVanillaIceCreamBuildOrHigher(Context context, Call call) {
        // report the anomaly for metrics purposes
        mAnomalyReporter.reportAnomaly(
                WATCHDOG_DISCONNECTED_STUCK_VOIP_CALL_UUID,
                WATCHDOG_DISCONNECTED_STUCK_VOIP_CALL_MSG);
        // only disconnect calls running on V and when the flag is enabled!
        PhoneAccountHandle phoneAccountHandle = call.getTargetPhoneAccount();
        PackageManager pm = context.getPackageManager();
        if (pm == null ||
                phoneAccountHandle == null ||
                phoneAccountHandle.getComponentName() == null) {
            return false;
        }
        String packageName = phoneAccountHandle.getComponentName().getPackageName();
        Log.d(this, "pah=[%s], user=[%s]", phoneAccountHandle, call.getAssociatedUser());
        ApplicationInfo applicationInfo;
        try {
            applicationInfo = pm.getApplicationInfoAsUser(
                    packageName,
                    0,
                    call.getAssociatedUser());
        } catch (Exception e) {
            Log.e(this, e, "iVICBOH: pm.getApplicationInfoAsUser(...) exception");
            return false;
        }
        int targetSdk = (applicationInfo == null) ? 0 : applicationInfo.targetSdkVersion;
        Log.i(this, "iVICBOH: packageName=[%s], sdk=[%d]", packageName, targetSdk);
        return targetSdk >= Build.VERSION_CODES.VANILLA_ICE_CREAM;
    }

    /**
     * Returns whether the action to disconnect the call when the Transitory state and
     * Intermediate state time expires is enabled or disabled.
+2 −1
Original line number Diff line number Diff line
@@ -375,7 +375,8 @@ public class TelecomSystem {

            CallAnomalyWatchdog callAnomalyWatchdog = new CallAnomalyWatchdog(
                    Executors.newSingleThreadScheduledExecutor(),
                    mLock, timeoutsAdapter, clockProxy, emergencyCallDiagnosticLogger);
                    mLock, mFeatureFlags, timeoutsAdapter, clockProxy,
                    emergencyCallDiagnosticLogger);

            TransactionManager transactionManager = TransactionManager.getInstance();

+6 −0
Original line number Diff line number Diff line
@@ -114,6 +114,12 @@ public class OutgoingCallTransaction extends VoipCallTransaction {
                            Log.d(TAG, "processTransaction: call done. id=" + call.getId());
                        }

                        if (mFeatureFlags.disconnectSelfManagedStuckStartupCalls()) {
                            // set to dialing so the CallAnomalyWatchdog gives the VoIP calls 1
                            // minute to timeout rather than 5 seconds.
                            mCallsManager.markCallAsDialing(call);
                        }

                        return CompletableFuture.completedFuture(
                                new VoipCallTransactionResult(
                                        VoipCallTransactionResult.RESULT_SUCCEED,
+1 −1
Original line number Diff line number Diff line
@@ -122,7 +122,7 @@ public class CallAnomalyWatchdogTest extends TelecomTestCase {
        doReturn(new ComponentName(mContext, CallTest.class))
                .when(mMockConnectionService).getComponentName();
        mCallAnomalyWatchdog = new CallAnomalyWatchdog(mTestScheduledExecutorService, mLock,
                mTimeouts, mMockClockProxy, mMockEmergencyCallDiagnosticLogger);
                mFeatureFlags, mTimeouts, mMockClockProxy, mMockEmergencyCallDiagnosticLogger);
        mCallAnomalyWatchdog.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
    }