Loading flags/telecom_anomaly_report_flags.aconfig +8 −0 Original line number Diff line number Diff line Loading @@ -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" } src/com/android/server/telecom/CallAnomalyWatchdog.java +72 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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){ Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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, Loading @@ -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"); Loading @@ -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. Loading src/com/android/server/telecom/TelecomSystem.java +2 −1 Original line number Diff line number Diff line Loading @@ -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(); Loading src/com/android/server/telecom/voip/OutgoingCallTransaction.java +6 −0 Original line number Diff line number Diff line Loading @@ -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, Loading tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java +1 −1 Original line number Diff line number Diff line Loading @@ -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); } Loading Loading
flags/telecom_anomaly_report_flags.aconfig +8 −0 Original line number Diff line number Diff line Loading @@ -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" }
src/com/android/server/telecom/CallAnomalyWatchdog.java +72 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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){ Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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, Loading @@ -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"); Loading @@ -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. Loading
src/com/android/server/telecom/TelecomSystem.java +2 −1 Original line number Diff line number Diff line Loading @@ -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(); Loading
src/com/android/server/telecom/voip/OutgoingCallTransaction.java +6 −0 Original line number Diff line number Diff line Loading @@ -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, Loading
tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java +1 −1 Original line number Diff line number Diff line Loading @@ -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); } Loading