diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java index 70cf97308ffba0de215c38f41336f14fab80297d..c727a6034006603d4db57b52d5c625db2298a098 100644 --- a/core/java/android/net/vcn/VcnManager.java +++ b/core/java/android/net/vcn/VcnManager.java @@ -114,6 +114,20 @@ public class VcnManager { public static final String VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY = "vcn_restricted_transports"; + /** + * Key for number of seconds to wait before entering safe mode + * + * <p>A VcnGatewayConnection will enter safe mode when it takes over the configured timeout to + * enter {@link ConnectedState}. + * + * <p>Defaults to 30, unless overridden by carrier config + * + * @hide + */ + @NonNull + public static final String VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY = + "vcn_safe_mode_timeout_seconds_key"; + /** * Key for maximum number of parallel SAs for tunnel aggregation * @@ -135,6 +149,7 @@ public class VcnManager { VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY, VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY, + VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY, VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY, }; diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig index 6956916af0f1f097b1b78023e5acdfefd1a5b142..67a1906d48ed01685b96eee31355f096e4fa08ef 100644 --- a/core/java/android/net/vcn/flags.aconfig +++ b/core/java/android/net/vcn/flags.aconfig @@ -5,4 +5,11 @@ flag { namespace: "vcn" description: "Feature flag for safe mode configurability" bug: "276358140" +} + +flag { + name: "safe_mode_timeout_config" + namespace: "vcn" + description: "Feature flag for adjustable safe mode timeout" + bug: "317406085" } \ No newline at end of file diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java index 9213d96ad4ca60229a37961abe286c064614ba0f..6ce86854007066dd5fe817bdace8bfc0068beeec 100644 --- a/services/core/java/com/android/server/vcn/VcnContext.java +++ b/services/core/java/com/android/server/vcn/VcnContext.java @@ -74,6 +74,10 @@ public class VcnContext { return mFeatureFlags; } + public boolean isFlagSafeModeTimeoutConfigEnabled() { + return mFeatureFlags.safeModeTimeoutConfig(); + } + /** * Verifies that the caller is running on the VcnContext Thread. * diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 54c97dd37941740cbcb36cd986091b1cb4638cc7..68132dcfc9a4c921232a40a3bb7e9fcb91b422b2 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -1242,9 +1242,28 @@ public class VcnGatewayConnection extends StateMachine { createScheduledAlarm( SAFEMODE_TIMEOUT_ALARM, delayedMessage, - mVcnContext.isInTestMode() - ? TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS_TEST_MODE) - : TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS)); + getSafeModeTimeoutMs(mVcnContext, mLastSnapshot, mSubscriptionGroup)); + } + + /** Gets the safe mode timeout */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static long getSafeModeTimeoutMs( + VcnContext vcnContext, TelephonySubscriptionSnapshot snapshot, ParcelUuid subGrp) { + final int defaultSeconds = + vcnContext.isInTestMode() + ? SAFEMODE_TIMEOUT_SECONDS_TEST_MODE + : SAFEMODE_TIMEOUT_SECONDS; + + final PersistableBundleWrapper carrierConfig = snapshot.getCarrierConfigForSubGrp(subGrp); + int resultSeconds = defaultSeconds; + + if (vcnContext.isFlagSafeModeTimeoutConfigEnabled() && carrierConfig != null) { + resultSeconds = + carrierConfig.getInt( + VcnManager.VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY, defaultSeconds); + } + + return TimeUnit.SECONDS.toMillis(resultSeconds); } private void cancelSafeModeAlarm() { diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java index 692c8a8f0898be31cc1a93fce09bf13d90415f4b..49665f7a330420a543a376d663ae817dd4bd87cb 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java @@ -27,15 +27,18 @@ import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY; import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR; +import static com.android.server.vcn.VcnGatewayConnection.SAFEMODE_TIMEOUT_SECONDS; import static com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration; import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession; import static com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent; +import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.CALLS_REAL_METHODS; @@ -55,6 +58,7 @@ import android.net.NetworkRequest; import android.net.TelephonyNetworkSpecifier; import android.net.vcn.VcnGatewayConnectionConfig; import android.net.vcn.VcnGatewayConnectionConfigTest; +import android.net.vcn.VcnManager; import android.net.vcn.VcnTransportInfo; import android.net.wifi.WifiInfo; import android.os.ParcelUuid; @@ -81,6 +85,7 @@ import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; /** Tests for TelephonySubscriptionTracker */ @RunWith(AndroidJUnit4.class) @@ -352,4 +357,71 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { any(Executor.class), any(ConnectivityDiagnosticsCallback.class)); } + + private void verifyGetSafeModeTimeoutMs( + boolean isInTestMode, + boolean isConfigTimeoutSupported, + PersistableBundleWrapper carrierConfig, + long expectedTimeoutMs) + throws Exception { + doReturn(isInTestMode).when(mVcnContext).isInTestMode(); + doReturn(isConfigTimeoutSupported).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled(); + + final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class); + doReturn(carrierConfig).when(snapshot).getCarrierConfigForSubGrp(TEST_SUB_GRP); + + final long result = + VcnGatewayConnection.getSafeModeTimeoutMs(mVcnContext, snapshot, TEST_SUB_GRP); + + assertEquals(expectedTimeoutMs, result); + } + + @Test + public void testGetSafeModeTimeoutMs_configTimeoutUnsupported() throws Exception { + verifyGetSafeModeTimeoutMs( + false /* isInTestMode */, + false /* isConfigTimeoutSupported */, + null /* carrierConfig */, + TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS)); + } + + @Test + public void testGetSafeModeTimeoutMs_configTimeoutSupported() throws Exception { + final int carrierConfigTimeoutSeconds = 20; + final PersistableBundleWrapper carrierConfig = mock(PersistableBundleWrapper.class); + doReturn(carrierConfigTimeoutSeconds) + .when(carrierConfig) + .getInt(eq(VcnManager.VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY), anyInt()); + + verifyGetSafeModeTimeoutMs( + false /* isInTestMode */, + true /* isConfigTimeoutSupported */, + carrierConfig, + TimeUnit.SECONDS.toMillis(carrierConfigTimeoutSeconds)); + } + + @Test + public void testGetSafeModeTimeoutMs_configTimeoutSupported_carrierConfigNull() + throws Exception { + verifyGetSafeModeTimeoutMs( + false /* isInTestMode */, + true /* isConfigTimeoutSupported */, + null /* carrierConfig */, + TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS)); + } + + @Test + public void testGetSafeModeTimeoutMs_configTimeoutOverrideTestModeDefault() throws Exception { + final int carrierConfigTimeoutSeconds = 20; + final PersistableBundleWrapper carrierConfig = mock(PersistableBundleWrapper.class); + doReturn(carrierConfigTimeoutSeconds) + .when(carrierConfig) + .getInt(eq(VcnManager.VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY), anyInt()); + + verifyGetSafeModeTimeoutMs( + true /* isInTestMode */, + true /* isConfigTimeoutSupported */, + carrierConfig, + TimeUnit.SECONDS.toMillis(carrierConfigTimeoutSeconds)); + } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index edced87427c8ee0f7c6b3f5a96fa3b91741076f7..342056ad0238523f07691ea2f91ca63bcbbd25c9 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -226,6 +226,7 @@ public class VcnGatewayConnectionTestBase { doReturn(mTestLooper.getLooper()).when(mVcnContext).getLooper(); doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider(); doReturn(mFeatureFlags).when(mVcnContext).getFeatureFlags(); + doReturn(true).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled(); doReturn(mUnderlyingNetworkController) .when(mDeps)