Merge "Adjust query frequency based on remaining TTL"
diff --git a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
index 9162546..9dad301 100644
--- a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
+++ b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
@@ -167,13 +167,18 @@
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
 
-        mRunTests = runAsShell(NETWORK_SETTINGS, TETHER_PRIVILEGED, () ->
-                mTm.isTetheringSupported());
+        mRunTests = isEthernetTetheringSupported();
         assumeTrue(mRunTests);
 
         mTetheredInterfaceRequester = new TetheredInterfaceRequester(mHandler, mEm);
     }
 
+    private boolean isEthernetTetheringSupported() throws Exception {
+        if (mEm == null) return false;
+
+        return runAsShell(NETWORK_SETTINGS, TETHER_PRIVILEGED, () -> mTm.isTetheringSupported());
+    }
+
     protected void maybeStopTapPacketReader(final TapPacketReader tapPacketReader)
             throws Exception {
         if (tapPacketReader != null) {
diff --git a/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java b/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
index d59d526..414aca3 100644
--- a/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
+++ b/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
@@ -45,6 +45,7 @@
 import com.android.metrics.KeepaliveLifetimeForCarrier;
 import com.android.metrics.KeepaliveLifetimePerCarrier;
 import com.android.modules.utils.BackgroundThread;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.CollectionUtils;
 import com.android.server.ConnectivityStatsLog;
 
@@ -251,6 +252,22 @@
         public long getElapsedRealtime() {
             return SystemClock.elapsedRealtime();
         }
+
+        /**
+         * Writes a DAILY_KEEPALIVE_INFO_REPORTED to ConnectivityStatsLog.
+         *
+         * @param dailyKeepaliveInfoReported the proto to write to statsD.
+         */
+        public void writeStats(DailykeepaliveInfoReported dailyKeepaliveInfoReported) {
+            ConnectivityStatsLog.write(
+                    ConnectivityStatsLog.DAILY_KEEPALIVE_INFO_REPORTED,
+                    dailyKeepaliveInfoReported.getDurationPerNumOfKeepalive().toByteArray(),
+                    dailyKeepaliveInfoReported.getKeepaliveLifetimePerCarrier().toByteArray(),
+                    dailyKeepaliveInfoReported.getKeepaliveRequests(),
+                    dailyKeepaliveInfoReported.getAutomaticKeepaliveRequests(),
+                    dailyKeepaliveInfoReported.getDistinctUserCount(),
+                    CollectionUtils.toIntArray(dailyKeepaliveInfoReported.getUidList()));
+        }
     }
 
     public KeepaliveStatsTracker(@NonNull Context context, @NonNull Handler handler) {
@@ -637,15 +654,15 @@
     /** Writes the stored metrics to ConnectivityStatsLog and resets.  */
     public void writeAndResetMetrics() {
         ensureRunningOnHandlerThread();
+        // Keepalive stats use repeated atoms, which are only supported on T+. If written to statsd
+        // on S- they will bootloop the system, so they must not be sent on S-. See b/289471411.
+        if (!SdkLevel.isAtLeastT()) {
+            Log.d(TAG, "KeepaliveStatsTracker is disabled before T, skipping write");
+            return;
+        }
+
         final DailykeepaliveInfoReported dailyKeepaliveInfoReported = buildAndResetMetrics();
-        ConnectivityStatsLog.write(
-                ConnectivityStatsLog.DAILY_KEEPALIVE_INFO_REPORTED,
-                dailyKeepaliveInfoReported.getDurationPerNumOfKeepalive().toByteArray(),
-                dailyKeepaliveInfoReported.getKeepaliveLifetimePerCarrier().toByteArray(),
-                dailyKeepaliveInfoReported.getKeepaliveRequests(),
-                dailyKeepaliveInfoReported.getAutomaticKeepaliveRequests(),
-                dailyKeepaliveInfoReported.getDistinctUserCount(),
-                CollectionUtils.toIntArray(dailyKeepaliveInfoReported.getUidList()));
+        mDependencies.writeStats(dailyKeepaliveInfoReported);
     }
 
     private void ensureRunningOnHandlerThread() {
diff --git a/service/src/com/android/server/connectivity/ProxyTracker.java b/service/src/com/android/server/connectivity/ProxyTracker.java
index 6a0918b..4415007 100644
--- a/service/src/com/android/server/connectivity/ProxyTracker.java
+++ b/service/src/com/android/server/connectivity/ProxyTracker.java
@@ -404,7 +404,7 @@
                 // network, so discount this case.
                 if (null == mGlobalProxy && !lp.getHttpProxy().getPacFileUrl()
                         .equals(defaultProxy.getPacFileUrl())) {
-                    throw new IllegalStateException("Unexpected discrepancy between proxy in LP of "
+                    Log.wtf(TAG, "Unexpected discrepancy between proxy in LP of "
                             + "default network and default proxy. The former has a PAC URL of "
                             + lp.getHttpProxy().getPacFileUrl() + " while the latter has "
                             + defaultProxy.getPacFileUrl());
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index cd3b650..454940f 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -1726,10 +1726,21 @@
         assertEquals(VpnManager.TYPE_VPN_SERVICE, ((VpnTransportInfo) ti).getType());
     }
 
-    private void assertDefaultProxy(ProxyInfo expected) {
+    private void assertDefaultProxy(ProxyInfo expected) throws Exception {
         assertEquals("Incorrect proxy config.", expected, mCM.getDefaultProxy());
         String expectedHost = expected == null ? null : expected.getHost();
         String expectedPort = expected == null ? null : String.valueOf(expected.getPort());
+
+        // ActivityThread may not have time to set it in the properties yet which will cause flakes.
+        // Wait for some time to deflake the test.
+        int attempt = 0;
+        while (!(Objects.equals(expectedHost, System.getProperty("http.proxyHost"))
+                && Objects.equals(expectedPort, System.getProperty("http.proxyPort")))
+                && attempt < 300) {
+            attempt++;
+            Log.d(TAG, "Wait for proxy being updated, attempt=" + attempt);
+            Thread.sleep(100);
+        }
         assertEquals("Incorrect proxy host system property.", expectedHost,
             System.getProperty("http.proxyHost"));
         assertEquals("Incorrect proxy port system property.", expectedPort,
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java b/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
index cfd3130..a7d5590 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
@@ -18,9 +18,13 @@
 
 import android.platform.test.annotations.FlakyTest;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
 public class HostsideConnOnActivityStartTest extends HostsideNetworkTestCase {
     private static final String TEST_CLASS = TEST_PKG + ".ConnOnActivityStartTest";
-    @Override
+    @Before
     public void setUp() throws Exception {
         super.setUp();
 
@@ -28,26 +32,30 @@
         installPackage(TEST_APP2_APK);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         super.tearDown();
 
         uninstallPackage(TEST_APP2_PKG, true);
     }
 
+    @Test
     public void testStartActivity_batterySaver() throws Exception {
         runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_batterySaver");
     }
 
+    @Test
     public void testStartActivity_dataSaver() throws Exception {
         runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_dataSaver");
     }
 
     @FlakyTest(bugId = 231440256)
+    @Test
     public void testStartActivity_doze() throws Exception {
         runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_doze");
     }
 
+    @Test
     public void testStartActivity_appStandby() throws Exception {
         runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_appStandby");
     }
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java
index 1312085..5d7ad62 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java
@@ -14,26 +14,33 @@
  * limitations under the License.
  */
 package com.android.cts.net;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
 public class HostsideNetworkCallbackTests extends HostsideNetworkTestCase {
 
-    @Override
-    protected void setUp() throws Exception {
+    @Before
+    public void setUp() throws Exception {
         super.setUp();
         uninstallPackage(TEST_APP2_PKG, false);
         installPackage(TEST_APP2_APK);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         super.tearDown();
         uninstallPackage(TEST_APP2_PKG, true);
     }
 
+    @Test
     public void testOnBlockedStatusChanged_dataSaver() throws Exception {
         runDeviceTests(TEST_PKG,
                 TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_dataSaver");
     }
 
+    @Test
     public void testOnBlockedStatusChanged_powerSaver() throws Exception {
         runDeviceTests(TEST_PKG,
                 TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_powerSaver");
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java
index fdb8876..40f5f59 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java
@@ -16,49 +16,59 @@
 
 package com.android.cts.net;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
 public class HostsideNetworkPolicyManagerTests extends HostsideNetworkTestCase {
-    @Override
-    protected void setUp() throws Exception {
+    @Before
+    public void setUp() throws Exception {
         super.setUp();
         uninstallPackage(TEST_APP2_PKG, false);
         installPackage(TEST_APP2_APK);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         super.tearDown();
         uninstallPackage(TEST_APP2_PKG, true);
     }
 
+    @Test
     public void testIsUidNetworkingBlocked_withUidNotBlocked() throws Exception {
         runDeviceTests(TEST_PKG,
                 TEST_PKG + ".NetworkPolicyManagerTest",
                 "testIsUidNetworkingBlocked_withUidNotBlocked");
     }
 
+    @Test
     public void testIsUidNetworkingBlocked_withSystemUid() throws Exception {
         runDeviceTests(TEST_PKG,
                 TEST_PKG + ".NetworkPolicyManagerTest", "testIsUidNetworkingBlocked_withSystemUid");
     }
 
+    @Test
     public void testIsUidNetworkingBlocked_withDataSaverMode() throws Exception {
         runDeviceTests(TEST_PKG,
                 TEST_PKG + ".NetworkPolicyManagerTest",
                 "testIsUidNetworkingBlocked_withDataSaverMode");
     }
 
+    @Test
     public void testIsUidNetworkingBlocked_withRestrictedNetworkingMode() throws Exception {
         runDeviceTests(TEST_PKG,
                 TEST_PKG + ".NetworkPolicyManagerTest",
                 "testIsUidNetworkingBlocked_withRestrictedNetworkingMode");
     }
 
+    @Test
     public void testIsUidNetworkingBlocked_withPowerSaverMode() throws Exception {
         runDeviceTests(TEST_PKG,
                 TEST_PKG + ".NetworkPolicyManagerTest",
                 "testIsUidNetworkingBlocked_withPowerSaverMode");
     }
 
+    @Test
     public void testIsUidRestrictedOnMeteredNetworks() throws Exception {
         runDeviceTests(TEST_PKG,
                 TEST_PKG + ".NetworkPolicyManagerTest", "testIsUidRestrictedOnMeteredNetworks");
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
index 2aa1032..c896168 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
@@ -16,28 +16,22 @@
 
 package com.android.cts.net;
 
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
 import com.android.ddmlib.Log;
-import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestResult.TestStatus;
 import com.android.modules.utils.build.testing.DeviceSdkLevel;
-import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.result.CollectingTestListener;
-import com.android.tradefed.result.TestDescription;
-import com.android.tradefed.result.TestResult;
-import com.android.tradefed.result.TestRunResult;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IAbi;
-import com.android.tradefed.testtype.IAbiReceiver;
-import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
 import com.android.tradefed.util.RunUtil;
 
-import java.io.FileNotFoundException;
-import java.util.Map;
+import org.junit.runner.RunWith;
 
-abstract class HostsideNetworkTestCase extends DeviceTestCase implements IAbiReceiver,
-        IBuildReceiver {
+@RunWith(DeviceJUnit4ClassRunner.class)
+abstract class HostsideNetworkTestCase extends BaseHostJUnit4Test {
     protected static final boolean DEBUG = false;
     protected static final String TAG = "HostsideNetworkTests";
     protected static final String TEST_PKG = "com.android.cts.net.hostside";
@@ -46,26 +40,7 @@
     protected static final String TEST_APP2_PKG = "com.android.cts.net.hostside.app2";
     protected static final String TEST_APP2_APK = "CtsHostsideNetworkTestsApp2.apk";
 
-    private IAbi mAbi;
-    private IBuildInfo mCtsBuild;
-
-    @Override
-    public void setAbi(IAbi abi) {
-        mAbi = abi;
-    }
-
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mCtsBuild = buildInfo;
-    }
-
-    @Override
     protected void setUp() throws Exception {
-        super.setUp();
-
-        assertNotNull(mAbi);
-        assertNotNull(mCtsBuild);
-
         DeviceSdkLevel deviceSdkLevel = new DeviceSdkLevel(getDevice());
         String testApk = deviceSdkLevel.isDeviceAtLeastT() ? TEST_APK_NEXT
                 : TEST_APK;
@@ -74,23 +49,20 @@
         installPackage(testApk);
     }
 
-    @Override
     protected void tearDown() throws Exception {
-        super.tearDown();
-
         uninstallPackage(TEST_PKG, true);
     }
 
-    protected void installPackage(String apk) throws FileNotFoundException,
-            DeviceNotAvailableException {
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
-        assertNull(getDevice().installPackage(buildHelper.getTestFile(apk),
-                false /* reinstall */, true /* grantPermissions */, "-t"));
+    protected void installPackage(String apk) throws DeviceNotAvailableException, TargetSetupError {
+        final DeviceTestRunOptions installOptions = new DeviceTestRunOptions(
+                null /* packageName */);
+        final int userId = getDevice().getCurrentUser();
+        installPackageAsUser(apk, true /* grantPermission */, userId, "-t");
     }
 
     protected void uninstallPackage(String packageName, boolean shouldSucceed)
             throws DeviceNotAvailableException {
-        final String result = getDevice().uninstallPackage(packageName);
+        final String result = uninstallPackage(packageName);
         if (shouldSucceed) {
             assertNull("uninstallPackage(" + packageName + ") failed: " + result, result);
         }
@@ -126,50 +98,6 @@
         fail("Package '" + packageName + "' not uinstalled after " + max_tries + " seconds");
     }
 
-    protected void runDeviceTests(String packageName, String testClassName)
-            throws DeviceNotAvailableException {
-        runDeviceTests(packageName, testClassName, null);
-    }
-
-    protected void runDeviceTests(String packageName, String testClassName, String methodName)
-            throws DeviceNotAvailableException {
-        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(packageName,
-                "androidx.test.runner.AndroidJUnitRunner", getDevice().getIDevice());
-
-        if (testClassName != null) {
-            if (methodName != null) {
-                testRunner.setMethodName(testClassName, methodName);
-            } else {
-                testRunner.setClassName(testClassName);
-            }
-        }
-
-        final CollectingTestListener listener = new CollectingTestListener();
-        getDevice().runInstrumentationTests(testRunner, listener);
-
-        final TestRunResult result = listener.getCurrentRunResults();
-        if (result.isRunFailure()) {
-            throw new AssertionError("Failed to successfully run device tests for "
-                    + result.getName() + ": " + result.getRunFailureMessage());
-        }
-
-        if (result.hasFailedTests()) {
-            // build a meaningful error message
-            StringBuilder errorBuilder = new StringBuilder("on-device tests failed:\n");
-            for (Map.Entry<TestDescription, TestResult> resultEntry :
-                    result.getTestResults().entrySet()) {
-                final TestStatus testStatus = resultEntry.getValue().getStatus();
-                if (!TestStatus.PASSED.equals(testStatus)
-                        && !TestStatus.ASSUMPTION_FAILURE.equals(testStatus)) {
-                    errorBuilder.append(resultEntry.getKey().toString());
-                    errorBuilder.append(":\n");
-                    errorBuilder.append(resultEntry.getValue().getStackTrace());
-                }
-            }
-            throw new AssertionError(errorBuilder.toString());
-        }
-    }
-
     protected int getUid(String packageName) throws DeviceNotAvailableException {
         final int currentUser = getDevice().getCurrentUser();
         final String uidLines = runCommand(
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
index 21c78b7..0977deb 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
@@ -16,30 +16,37 @@
 
 package com.android.cts.net;
 
+import static org.junit.Assert.fail;
+
 import android.platform.test.annotations.SecurityTest;
 
 import com.android.ddmlib.Log;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.util.RunUtil;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
 public class HostsideRestrictBackgroundNetworkTests extends HostsideNetworkTestCase {
 
-    @Override
-    protected void setUp() throws Exception {
+    @Before
+    public void setUp() throws Exception {
         super.setUp();
 
         uninstallPackage(TEST_APP2_PKG, false);
         installPackage(TEST_APP2_APK);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         super.tearDown();
 
         uninstallPackage(TEST_APP2_PKG, true);
     }
 
     @SecurityTest
+    @Test
     public void testDataWarningReceiver() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".DataWarningReceiverTest",
                 "testSnoozeWarningNotReceived");
@@ -49,26 +56,31 @@
      * Data Saver Mode tests. *
      **************************/
 
+    @Test
     public void testDataSaverMode_disabled() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
                 "testGetRestrictBackgroundStatus_disabled");
     }
 
+    @Test
     public void testDataSaverMode_whitelisted() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
                 "testGetRestrictBackgroundStatus_whitelisted");
     }
 
+    @Test
     public void testDataSaverMode_enabled() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
                 "testGetRestrictBackgroundStatus_enabled");
     }
 
+    @Test
     public void testDataSaverMode_blacklisted() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
                 "testGetRestrictBackgroundStatus_blacklisted");
     }
 
+    @Test
     public void testDataSaverMode_reinstall() throws Exception {
         final int oldUid = getUid(TEST_APP2_PKG);
 
@@ -85,11 +97,13 @@
         assertRestrictBackgroundWhitelist(newUid, false);
     }
 
+    @Test
     public void testDataSaverMode_requiredWhitelistedPackages() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
                 "testGetRestrictBackgroundStatus_requiredWhitelistedPackages");
     }
 
+    @Test
     public void testDataSaverMode_broadcastNotSentOnUnsupportedDevices() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
                 "testBroadcastNotSentOnUnsupportedDevices");
@@ -99,21 +113,25 @@
      * Battery Saver Mode tests. *
      *****************************/
 
+    @Test
     public void testBatterySaverModeMetered_disabled() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeMeteredTest",
                 "testBackgroundNetworkAccess_disabled");
     }
 
+    @Test
     public void testBatterySaverModeMetered_whitelisted() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeMeteredTest",
                 "testBackgroundNetworkAccess_whitelisted");
     }
 
+    @Test
     public void testBatterySaverModeMetered_enabled() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeMeteredTest",
                 "testBackgroundNetworkAccess_enabled");
     }
 
+    @Test
     public void testBatterySaverMode_reinstall() throws Exception {
         if (!isDozeModeEnabled()) {
             Log.w(TAG, "testBatterySaverMode_reinstall() skipped because device does not support "
@@ -131,16 +149,19 @@
         assertPowerSaveModeWhitelist(TEST_APP2_PKG, false);
     }
 
+    @Test
     public void testBatterySaverModeNonMetered_disabled() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeNonMeteredTest",
                 "testBackgroundNetworkAccess_disabled");
     }
 
+    @Test
     public void testBatterySaverModeNonMetered_whitelisted() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeNonMeteredTest",
                 "testBackgroundNetworkAccess_whitelisted");
     }
 
+    @Test
     public void testBatterySaverModeNonMetered_enabled() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeNonMeteredTest",
                 "testBackgroundNetworkAccess_enabled");
@@ -150,26 +171,31 @@
      * App idle tests. *
      *******************/
 
+    @Test
     public void testAppIdleMetered_disabled() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
                 "testBackgroundNetworkAccess_disabled");
     }
 
+    @Test
     public void testAppIdleMetered_whitelisted() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
                 "testBackgroundNetworkAccess_whitelisted");
     }
 
+    @Test
     public void testAppIdleMetered_tempWhitelisted() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
                 "testBackgroundNetworkAccess_tempWhitelisted");
     }
 
+    @Test
     public void testAppIdleMetered_enabled() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
                 "testBackgroundNetworkAccess_enabled");
     }
 
+    @Test
     public void testAppIdleMetered_idleWhitelisted() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
                 "testAppIdleNetworkAccess_idleWhitelisted");
@@ -180,41 +206,50 @@
     //    public void testAppIdle_reinstall() throws Exception {
     //    }
 
+    @Test
     public void testAppIdleNonMetered_disabled() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
                 "testBackgroundNetworkAccess_disabled");
     }
 
+
+    @Test
     public void testAppIdleNonMetered_whitelisted() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
                 "testBackgroundNetworkAccess_whitelisted");
     }
 
+    @Test
     public void testAppIdleNonMetered_tempWhitelisted() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
                 "testBackgroundNetworkAccess_tempWhitelisted");
     }
 
+    @Test
     public void testAppIdleNonMetered_enabled() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
                 "testBackgroundNetworkAccess_enabled");
     }
 
+    @Test
     public void testAppIdleNonMetered_idleWhitelisted() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
                 "testAppIdleNetworkAccess_idleWhitelisted");
     }
 
+    @Test
     public void testAppIdleNonMetered_whenCharging() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
                 "testAppIdleNetworkAccess_whenCharging");
     }
 
+    @Test
     public void testAppIdleMetered_whenCharging() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
                 "testAppIdleNetworkAccess_whenCharging");
     }
 
+    @Test
     public void testAppIdle_toast() throws Exception {
         // Check that showing a toast doesn't bring an app out of standby
         runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
@@ -225,21 +260,25 @@
      * Doze Mode tests. *
      ********************/
 
+    @Test
     public void testDozeModeMetered_disabled() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
                 "testBackgroundNetworkAccess_disabled");
     }
 
+    @Test
     public void testDozeModeMetered_whitelisted() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
                 "testBackgroundNetworkAccess_whitelisted");
     }
 
+    @Test
     public void testDozeModeMetered_enabled() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
                 "testBackgroundNetworkAccess_enabled");
     }
 
+    @Test
     public void testDozeModeMetered_enabledButWhitelistedOnNotificationAction() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
                 "testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction");
@@ -250,21 +289,25 @@
     //    public void testDozeMode_reinstall() throws Exception {
     //    }
 
+    @Test
     public void testDozeModeNonMetered_disabled() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
                 "testBackgroundNetworkAccess_disabled");
     }
 
+    @Test
     public void testDozeModeNonMetered_whitelisted() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
                 "testBackgroundNetworkAccess_whitelisted");
     }
 
+    @Test
     public void testDozeModeNonMetered_enabled() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
                 "testBackgroundNetworkAccess_enabled");
     }
 
+    @Test
     public void testDozeModeNonMetered_enabledButWhitelistedOnNotificationAction()
             throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
@@ -275,46 +318,55 @@
      * Mixed modes tests. *
      **********************/
 
+    @Test
     public void testDataAndBatterySaverModes_meteredNetwork() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
                 "testDataAndBatterySaverModes_meteredNetwork");
     }
 
+    @Test
     public void testDataAndBatterySaverModes_nonMeteredNetwork() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
                 "testDataAndBatterySaverModes_nonMeteredNetwork");
     }
 
+    @Test
     public void testDozeAndBatterySaverMode_powerSaveWhitelists() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
                 "testDozeAndBatterySaverMode_powerSaveWhitelists");
     }
 
+    @Test
     public void testDozeAndAppIdle_powerSaveWhitelists() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
                 "testDozeAndAppIdle_powerSaveWhitelists");
     }
 
+    @Test
     public void testAppIdleAndDoze_tempPowerSaveWhitelists() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
                 "testAppIdleAndDoze_tempPowerSaveWhitelists");
     }
 
+    @Test
     public void testAppIdleAndBatterySaver_tempPowerSaveWhitelists() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
                 "testAppIdleAndBatterySaver_tempPowerSaveWhitelists");
     }
 
+    @Test
     public void testDozeAndAppIdle_appIdleWhitelist() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
                 "testDozeAndAppIdle_appIdleWhitelist");
     }
 
+    @Test
     public void testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
                 "testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists");
     }
 
+    @Test
     public void testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
                 "testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists");
@@ -323,11 +375,14 @@
     /**************************
      * Restricted mode tests. *
      **************************/
+
+    @Test
     public void testNetworkAccess_restrictedMode() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".RestrictedModeTest",
                 "testNetworkAccess");
     }
 
+    @Test
     public void testNetworkAccess_restrictedMode_withBatterySaver() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".RestrictedModeTest",
                 "testNetworkAccess_withBatterySaver");
@@ -337,10 +392,12 @@
      * Expedited job tests. *
      ************************/
 
+    @Test
     public void testMeteredNetworkAccess_expeditedJob() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".ExpeditedJobMeteredTest");
     }
 
+    @Test
     public void testNonMeteredNetworkAccess_expeditedJob() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".ExpeditedJobNonMeteredTest");
     }
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideSelfDeclaredNetworkCapabilitiesCheckTest.java b/tests/cts/hostside/src/com/android/cts/net/HostsideSelfDeclaredNetworkCapabilitiesCheckTest.java
index 4c2985d..c3bdb6d 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideSelfDeclaredNetworkCapabilitiesCheckTest.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideSelfDeclaredNetworkCapabilitiesCheckTest.java
@@ -15,6 +15,8 @@
  */
 package com.android.cts.net;
 
+import org.junit.Test;
+
 public class HostsideSelfDeclaredNetworkCapabilitiesCheckTest extends HostsideNetworkTestCase {
 
     private static final String TEST_WITH_PROPERTY_IN_CURRENT_SDK_APK =
@@ -34,6 +36,7 @@
             "requestNetwork_withoutRequestCapabilities";
 
 
+    @Test
     public void testRequestNetworkInCurrentSdkWithProperty() throws Exception {
         uninstallPackage(TEST_APP_PKG, false);
         installPackage(TEST_WITH_PROPERTY_IN_CURRENT_SDK_APK);
@@ -48,6 +51,7 @@
         uninstallPackage(TEST_APP_PKG, true);
     }
 
+    @Test
     public void testRequestNetworkInCurrentSdkWithoutProperty() throws Exception {
         uninstallPackage(TEST_APP_PKG, false);
         installPackage(TEST_WITHOUT_PROPERTY_IN_CURRENT_SDK_APK);
@@ -62,6 +66,7 @@
         uninstallPackage(TEST_APP_PKG, true);
     }
 
+    @Test
     public void testRequestNetworkInSdk33() throws Exception {
         uninstallPackage(TEST_APP_PKG, false);
         installPackage(TEST_IN_SDK_33_APK);
@@ -75,6 +80,7 @@
         uninstallPackage(TEST_APP_PKG, true);
     }
 
+    @Test
     public void testReinstallPackageWillUpdateProperty() throws Exception {
         uninstallPackage(TEST_APP_PKG, false);
         installPackage(TEST_WITHOUT_PROPERTY_IN_CURRENT_SDK_APK);
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
index 3ca4775..242fd5d 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
@@ -18,95 +18,116 @@
 
 import android.platform.test.annotations.RequiresDevice;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
 public class HostsideVpnTests extends HostsideNetworkTestCase {
 
-    @Override
-    protected void setUp() throws Exception {
+    @Before
+    public void setUp() throws Exception {
         super.setUp();
 
         uninstallPackage(TEST_APP2_PKG, false);
         installPackage(TEST_APP2_APK);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         super.tearDown();
 
         uninstallPackage(TEST_APP2_PKG, true);
     }
 
+    @Test
     public void testChangeUnderlyingNetworks() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testChangeUnderlyingNetworks");
     }
 
+    @Test
     public void testDefault() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testDefault");
     }
 
+    @Test
     public void testAppAllowed() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testAppAllowed");
     }
 
+    @Test
     public void testAppDisallowed() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testAppDisallowed");
     }
 
+    @Test
     public void testSocketClosed() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testSocketClosed");
     }
 
+    @Test
     public void testGetConnectionOwnerUidSecurity() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testGetConnectionOwnerUidSecurity");
     }
 
+    @Test
     public void testSetProxy() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testSetProxy");
     }
 
+    @Test
     public void testSetProxyDisallowedApps() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testSetProxyDisallowedApps");
     }
 
+    @Test
     public void testNoProxy() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testNoProxy");
     }
 
+    @Test
     public void testBindToNetworkWithProxy() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testBindToNetworkWithProxy");
     }
 
+    @Test
     public void testVpnMeterednessWithNoUnderlyingNetwork() throws Exception {
         runDeviceTests(
                 TEST_PKG, TEST_PKG + ".VpnTest", "testVpnMeterednessWithNoUnderlyingNetwork");
     }
 
+    @Test
     public void testVpnMeterednessWithNullUnderlyingNetwork() throws Exception {
         runDeviceTests(
                 TEST_PKG, TEST_PKG + ".VpnTest", "testVpnMeterednessWithNullUnderlyingNetwork");
     }
 
+    @Test
     public void testVpnMeterednessWithNonNullUnderlyingNetwork() throws Exception {
         runDeviceTests(
                 TEST_PKG, TEST_PKG + ".VpnTest", "testVpnMeterednessWithNonNullUnderlyingNetwork");
     }
 
+    @Test
     public void testAlwaysMeteredVpnWithNullUnderlyingNetwork() throws Exception {
         runDeviceTests(
                 TEST_PKG, TEST_PKG + ".VpnTest", "testAlwaysMeteredVpnWithNullUnderlyingNetwork");
     }
 
     @RequiresDevice // Keepalive is not supported on virtual hardware
+    @Test
     public void testAutomaticOnOffKeepaliveModeClose() throws Exception {
         runDeviceTests(
                 TEST_PKG, TEST_PKG + ".VpnTest", "testAutomaticOnOffKeepaliveModeClose");
     }
 
     @RequiresDevice // Keepalive is not supported on virtual hardware
+    @Test
     public void testAutomaticOnOffKeepaliveModeNoClose() throws Exception {
         runDeviceTests(
                 TEST_PKG, TEST_PKG + ".VpnTest", "testAutomaticOnOffKeepaliveModeNoClose");
     }
 
+    @Test
     public void testAlwaysMeteredVpnWithNonNullUnderlyingNetwork() throws Exception {
         runDeviceTests(
                 TEST_PKG,
@@ -114,31 +135,38 @@
                 "testAlwaysMeteredVpnWithNonNullUnderlyingNetwork");
     }
 
+    @Test
     public void testB141603906() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testB141603906");
     }
 
+    @Test
     public void testDownloadWithDownloadManagerDisallowed() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest",
                 "testDownloadWithDownloadManagerDisallowed");
     }
 
+    @Test
     public void testExcludedRoutes() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testExcludedRoutes");
     }
 
+    @Test
     public void testIncludedRoutes() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testIncludedRoutes");
     }
 
+    @Test
     public void testInterleavedRoutes() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testInterleavedRoutes");
     }
 
+    @Test
     public void testBlockIncomingPackets() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testBlockIncomingPackets");
     }
 
+    @Test
     public void testSetVpnDefaultForUids() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testSetVpnDefaultForUids");
     }
diff --git a/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java b/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java
index 0d2e540..0d1b548 100644
--- a/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java
@@ -19,6 +19,8 @@
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
+import static com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
+import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import static com.android.testutils.HandlerUtils.visibleOnHandlerThread;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -31,6 +33,7 @@
 import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import android.content.BroadcastReceiver;
@@ -62,6 +65,7 @@
 import com.android.testutils.HandlerUtils;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -103,6 +107,8 @@
                 .build();
     }
 
+    @Rule public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
     private HandlerThread mHandlerThread;
     private Handler mTestHandler;
 
@@ -1192,4 +1198,43 @@
                     expectKeepaliveCarrierStats1, expectKeepaliveCarrierStats2
                 });
     }
+
+    @Test
+    @IgnoreAfter(Build.VERSION_CODES.S_V2)
+    public void testWriteMetrics_doNothingBeforeT() {
+        // Keepalive stats use repeated atoms, which are only supported on T+. If written to statsd
+        // on S- they will bootloop the system, so they must not be sent on S-. See b/289471411.
+        final int writeTime = 1000;
+        setElapsedRealtime(writeTime);
+        visibleOnHandlerThread(mTestHandler, () -> mKeepaliveStatsTracker.writeAndResetMetrics());
+        verify(mDependencies, never()).writeStats(any());
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testWriteMetrics() {
+        final int writeTime = 1000;
+
+        final ArgumentCaptor<DailykeepaliveInfoReported> dailyKeepaliveInfoReportedCaptor =
+                ArgumentCaptor.forClass(DailykeepaliveInfoReported.class);
+
+        setElapsedRealtime(writeTime);
+        visibleOnHandlerThread(mTestHandler, () -> mKeepaliveStatsTracker.writeAndResetMetrics());
+        // Ensure writeStats is called with the correct DailykeepaliveInfoReported metrics.
+        verify(mDependencies).writeStats(dailyKeepaliveInfoReportedCaptor.capture());
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported =
+                dailyKeepaliveInfoReportedCaptor.getValue();
+
+        // Same as the no keepalive case
+        final int[] expectRegisteredDurations = new int[] {writeTime};
+        final int[] expectActiveDurations = new int[] {writeTime};
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported,
+                /* expectRequestsCount= */ 0,
+                /* expectAutoRequestsCount= */ 0,
+                /* expectAppUids= */ new int[0],
+                expectRegisteredDurations,
+                expectActiveDurations,
+                new KeepaliveCarrierStats[0]);
+    }
 }
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index dc50773..7829cb6 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -2768,23 +2768,30 @@
                 new PersistableBundle());
     }
 
-    private void verifyMobikeTriggered(List<Network> expected) {
+    private void verifyMobikeTriggered(List<Network> expected, int retryIndex) {
+        // Verify retry is scheduled
+        final long expectedDelayMs = mTestDeps.getValidationFailRecoveryMs(retryIndex);
+        final ArgumentCaptor<Long> delayCaptor = ArgumentCaptor.forClass(Long.class);
+        verify(mExecutor, times(retryIndex + 1)).schedule(
+                any(Runnable.class), delayCaptor.capture(), eq(TimeUnit.MILLISECONDS));
+        final List<Long> delays = delayCaptor.getAllValues();
+        assertEquals(expectedDelayMs, (long) delays.get(delays.size() - 1));
+
         final ArgumentCaptor<Network> networkCaptor = ArgumentCaptor.forClass(Network.class);
-        verify(mIkeSessionWrapper).setNetwork(networkCaptor.capture(),
-                anyInt() /* ipVersion */, anyInt() /* encapType */, anyInt() /* keepaliveDelay */);
+        verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS + expectedDelayMs))
+                .setNetwork(networkCaptor.capture(), anyInt() /* ipVersion */,
+                        anyInt() /* encapType */, anyInt() /* keepaliveDelay */);
         assertEquals(expected, Collections.singletonList(networkCaptor.getValue()));
     }
 
     @Test
     public void testDataStallInIkev2VpnMobikeDisabled() throws Exception {
-        verifySetupPlatformVpn(
+        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
                 createIkeConfig(createIkeConnectInfo(), false /* isMobikeEnabled */));
 
         doReturn(TEST_NETWORK).when(mMockNetworkAgent).getNetwork();
-        final ConnectivityDiagnosticsCallback connectivityDiagCallback =
-                getConnectivityDiagCallback();
-        final DataStallReport report = createDataStallReport();
-        connectivityDiagCallback.onDataStallSuspected(report);
+        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
 
         // Should not trigger MOBIKE if MOBIKE is not enabled
         verify(mIkeSessionWrapper, never()).setNetwork(any() /* network */,
@@ -2797,19 +2804,11 @@
                 createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
 
         doReturn(TEST_NETWORK).when(mMockNetworkAgent).getNetwork();
-        final ConnectivityDiagnosticsCallback connectivityDiagCallback =
-                getConnectivityDiagCallback();
-        final DataStallReport report = createDataStallReport();
-        connectivityDiagCallback.onDataStallSuspected(report);
-
+        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
         // Verify MOBIKE is triggered
-        verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks());
-
-        // Expect to skip other data stall event if MOBIKE was started.
-        reset(mIkeSessionWrapper);
-        connectivityDiagCallback.onDataStallSuspected(report);
-        verify(mIkeSessionWrapper, never()).setNetwork(any() /* network */,
-                anyInt() /* ipVersion */, anyInt() /* encapType */, anyInt() /* keepaliveDelay */);
+        verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(),
+                0 /* retryIndex */);
 
         reset(mIkev2SessionCreator);
 
@@ -2819,14 +2818,6 @@
                 NetworkAgent.VALIDATION_STATUS_VALID);
         verify(mIkev2SessionCreator, never()).createIkeSession(
                 any(), any(), any(), any(), any(), any());
-
-        // Send invalid result to verify no ike session reset since the data stall suspected
-        // variables(timer counter and boolean) was reset.
-        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
-                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
-        verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), anyLong(), any());
-        verify(mIkev2SessionCreator, never()).createIkeSession(
-                any(), any(), any(), any(), any(), any());
     }
 
     @Test
@@ -2834,31 +2825,46 @@
         final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
                 createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
 
-        final ConnectivityDiagnosticsCallback connectivityDiagCallback =
-                getConnectivityDiagCallback();
-
+        int retry = 0;
         doReturn(TEST_NETWORK).when(mMockNetworkAgent).getNetwork();
-        final DataStallReport report = createDataStallReport();
-        connectivityDiagCallback.onDataStallSuspected(report);
-
-        verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks());
+        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+        verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(),
+                retry++);
 
         reset(mIkev2SessionCreator);
 
+        // Second validation status update.
+        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+        verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(),
+                retry++);
+
+        // Use real delay to verify reset session will not be performed if there is an existing
+        // recovery for resetting the session.
+        mExecutor.delayMs = TestExecutor.REAL_DELAY;
+        mExecutor.executeDirect = true;
         // Send validation status update should result in ike session reset.
         ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
                 NetworkAgent.VALIDATION_STATUS_NOT_VALID);
 
-        // Verify reset is scheduled and run.
-        verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), anyLong(), any());
+        // Verify session reset is scheduled
+        long expectedDelay = mTestDeps.getValidationFailRecoveryMs(retry++);
+        final ArgumentCaptor<Long> delayCaptor = ArgumentCaptor.forClass(Long.class);
+        verify(mExecutor, times(retry)).schedule(any(Runnable.class), delayCaptor.capture(),
+                eq(TimeUnit.MILLISECONDS));
+        final List<Long> delays = delayCaptor.getAllValues();
+        assertEquals(expectedDelay, (long) delays.get(delays.size() - 1));
 
         // Another invalid status reported should not trigger other scheduled recovery.
-        reset(mExecutor);
+        expectedDelay = mTestDeps.getValidationFailRecoveryMs(retry++);
         ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
                 NetworkAgent.VALIDATION_STATUS_NOT_VALID);
-        verify(mExecutor, never()).schedule(any(Runnable.class), anyLong(), any());
+        verify(mExecutor, never()).schedule(
+                any(Runnable.class), eq(expectedDelay), eq(TimeUnit.MILLISECONDS));
 
-        verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS))
+        // Verify that session being reset
+        verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS + expectedDelay))
                 .createIkeSession(any(), any(), any(), any(), any(), any());
     }
 
@@ -3137,6 +3143,12 @@
         }
 
         @Override
+        public long getValidationFailRecoveryMs(int retryCount) {
+            // Simply return retryCount as the delay seconds for retrying.
+            return retryCount * 100L;
+        }
+
+        @Override
         public ScheduledThreadPoolExecutor newScheduledThreadPoolExecutor() {
             return mExecutor;
         }