Generate mobile data traffic in AtomTests instead of StatsdCtsFgActivity

Currently, there is a helper function located in
StatsdCtsForegroundActivity which is helping hostside tests to
bring up cellular data connection. However, it seems there is
no good way from hostside test to know the result of such operation.

Move the helper function to AtomTests so the hostside test could
have better control via the result of TestRunner.

This is a no-op refactoring. Better handling of test result will
be in addressed in subsequent patches.

Test: atest android.cts.statsd.atom.UidAtomTests#testMobileBytesTransfer
Test: atest android.cts.statsd.atom.UidAtomTests#testMobileBytesTransferByFgBg
Bug: 153174569
Change-Id: Ia3fc029a7ee779aac3547b6069170a3b03c1b5ac
diff --git a/tests/apps/statsdapp/Android.bp b/tests/apps/statsdapp/Android.bp
index b24d1c3..eabea94 100644
--- a/tests/apps/statsdapp/Android.bp
+++ b/tests/apps/statsdapp/Android.bp
@@ -44,6 +44,7 @@
         "compatibility-device-util-axt",
         "androidx.legacy_legacy-support-v4",
         "androidx.test.rules",
+        "cts-net-utils",
     ],
     jni_libs: ["liblmkhelper"],
     compile_multilib: "both",
diff --git a/tests/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java b/tests/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
index d08a3af..3f1a18b 100644
--- a/tests/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
+++ b/tests/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
@@ -50,6 +50,11 @@
 import android.location.LocationListener;
 import android.location.LocationManager;
 import android.media.MediaPlayer;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.cts.util.CtsNetUtils;
 import android.net.wifi.WifiManager;
 import android.os.AsyncTask;
 import android.os.Bundle;
@@ -64,12 +69,15 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.ShellIdentityUtils;
 
 import org.junit.Test;
 
+import java.net.HttpURLConnection;
+import java.net.URL;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
@@ -745,6 +753,108 @@
 
     }
 
+    /**
+     * Bring up and generate some traffic on cellular data connection.
+     */
+    @Test
+    public void testGenerateMobileTraffic() throws Exception {
+        final Context context = InstrumentationRegistry.getContext();
+        doGenerateNetworkTraffic(context, NetworkCapabilities.TRANSPORT_CELLULAR);
+    }
+
+    // Constants which are locally used by doGenerateNetworkTraffic.
+    private static final int NETWORK_TIMEOUT_MILLIS = 15000;
+    private static final String HTTPS_HOST_URL =
+            "https://connectivitycheck.gstatic.com/generate_204";
+    // Minimum and Maximum of iterations of exercise host, @see #doGenerateNetworkTraffic.
+    private static final int MIN_EXERCISE_HOST_ITERATIONS = 1;
+    private static final int MAX_EXERCISE_HOST_ITERATIONS = 19;
+
+    private void doGenerateNetworkTraffic(@NonNull Context context,
+            @NetworkCapabilities.Transport int transport) throws InterruptedException {
+        final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
+        final NetworkRequest request = new NetworkRequest.Builder().addCapability(
+                NetworkCapabilities.NET_CAPABILITY_INTERNET).addTransportType(transport).build();
+        final CtsNetUtils.TestNetworkCallback callback = new CtsNetUtils.TestNetworkCallback();
+
+        // Request network, and make http query when the network is available.
+        cm.requestNetwork(request, callback);
+
+        // If network is not available, throws IllegalStateException.
+        final Network network = callback.waitForAvailable();
+        if (network == null) {
+            throw new IllegalStateException("network "
+                    + NetworkCapabilities.transportNameOf(transport) + " is not available.");
+        }
+
+        final long startTime = SystemClock.elapsedRealtime();
+        try {
+            // Since history of network stats only have 2 hours of resolution, when it is
+            // being queried, service will assume that history network stats has uniform
+            // distribution and return a fraction of network stats that is originally
+            // subject to 2 hours. To be specific:
+            //    <returned network stats> = <total network stats> * <duration> / 2 hour,
+            // assuming the duration can fit in a 2 hours bucket.
+            // In the other hand, in statsd, the network stats is queried since boot,
+            // that means in order to assert non-zero packet counts, either the test should
+            // be run after enough time since boot, or the packet counts generated here
+            // should be enough. That is to say:
+            //   <total packet counts> * <up time> / 2 hour >= 1,
+            // or
+            //   iterations >= 2 hour / (<up time> * <packets per iteration>)
+            // Thus, iterations can be chosen based on the factors above to make this
+            // function generate enough packets in each direction to accommodate enough
+            // packet counts for a fraction of history bucket.
+            final double iterations = (TimeUnit.HOURS.toMillis(2) / startTime / 7);
+            // While just enough iterations are going to make the test flaky, add a 20%
+            // buffer to stabilize it and make sure it's in a reasonable range, so it won't
+            // consumes more than 100kb of traffic, or generates 0 byte of traffic.
+            final int augmentedIterations =
+                    (int) Math.max(iterations * 1.2, MIN_EXERCISE_HOST_ITERATIONS);
+            if (augmentedIterations > MAX_EXERCISE_HOST_ITERATIONS) {
+                throw new IllegalStateException("Exceeded max allowed iterations"
+                        + ", iterations=" + augmentedIterations
+                        + ", uptime=" + TimeUnit.MILLISECONDS.toSeconds(startTime) + "s");
+            }
+
+            for (int i = 0; i < augmentedIterations; i++) {
+                // By observing results of "dumpsys netstats --uid", typically the single
+                // run of the https request below generates 4200/1080 rx/tx bytes with
+                // around 7/9 rx/tx packets.
+                // This blocks the thread of NetworkCallback, thus no other event
+                // can be processed before return.
+                exerciseRemoteHost(cm, network, new URL(HTTPS_HOST_URL));
+            }
+            Log.i(TAG, "exerciseRemoteHost successful in " + (SystemClock.elapsedRealtime()
+                    - startTime) + " ms with iterations=" + augmentedIterations
+                    + ", uptime=" + TimeUnit.MILLISECONDS.toSeconds(startTime) + "s");
+        } catch (Exception e) {
+            Log.e(TAG, "exerciseRemoteHost failed in " + (SystemClock.elapsedRealtime()
+                    - startTime) + " ms: " + e);
+        } finally {
+            cm.unregisterNetworkCallback(callback);
+        }
+    }
+
+    /**
+     * Generate traffic on specified network.
+     */
+    private void exerciseRemoteHost(@NonNull ConnectivityManager cm, @NonNull Network network,
+            @NonNull URL url) throws Exception {
+        cm.bindProcessToNetwork(network);
+        HttpURLConnection urlc = null;
+        try {
+            urlc = (HttpURLConnection) network.openConnection(url);
+            urlc.setConnectTimeout(NETWORK_TIMEOUT_MILLIS);
+            urlc.setUseCaches(false);
+            urlc.connect();
+        } finally {
+            if (urlc != null) {
+                urlc.disconnect();
+            }
+        }
+    }
+
     // ------- Helper methods
 
     /** Puts the current thread to sleep. */
diff --git a/tests/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundActivity.java b/tests/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundActivity.java
index 890a52b..c8c02fc 100644
--- a/tests/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundActivity.java
+++ b/tests/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundActivity.java
@@ -27,25 +27,15 @@
 import android.graphics.Color;
 import android.graphics.Point;
 import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
 
-import androidx.annotation.NonNull;
-
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.concurrent.TimeUnit;
-
 /** An activity (to be run as a foreground process) which performs one of a number of actions. */
 public class StatsdCtsForegroundActivity extends Activity {
     private static final String TAG = StatsdCtsForegroundActivity.class.getSimpleName();
@@ -58,18 +48,11 @@
     public static final String ACTION_SHOW_NOTIFICATION = "action.show_notification";
     public static final String ACTION_CRASH = "action.crash";
     public static final String ACTION_CREATE_CHANNEL_GROUP = "action.create_channel_group";
-    public static final String ACTION_GENERATE_MOBILE_TRAFFIC = "action.generate_mobile_traffic";
     public static final String ACTION_POLL_NETWORK_STATS = "action.poll_network_stats";
 
     public static final int SLEEP_OF_ACTION_SLEEP_WHILE_TOP = 2_000;
     public static final int SLEEP_OF_ACTION_SHOW_APPLICATION_OVERLAY = 2_000;
     public static final int LONG_SLEEP_WHILE_TOP = 60_000;
-    private static final int NETWORK_TIMEOUT_MILLIS = 15000;
-    private static final String HTTPS_HOST_URL =
-            "https://connectivitycheck.gstatic.com/generate_204";
-    // Minimum and Maximum of iterations of exercise host, @see #doGenerateNetworkTraffic.
-    private static final int MIN_EXERCISE_HOST_ITERATIONS = 1;
-    private static final int MAX_EXERCISE_HOST_ITERATIONS = 19;
 
     @Override
     public void onCreate(Bundle bundle) {
@@ -106,9 +89,6 @@
             case ACTION_CREATE_CHANNEL_GROUP:
                 doCreateChannelGroup();
                 break;
-            case ACTION_GENERATE_MOBILE_TRAFFIC:
-                doGenerateNetworkTraffic(NetworkCapabilities.TRANSPORT_CELLULAR);
-                break;
             case ACTION_POLL_NETWORK_STATS:
                 doPollNetworkStats();
                 break;
@@ -194,87 +174,6 @@
         finish();
     }
 
-    private void doGenerateNetworkTraffic(@NetworkCapabilities.Transport int transport) {
-        final ConnectivityManager cm = getSystemService(ConnectivityManager.class);
-        final NetworkRequest request = new NetworkRequest.Builder().addCapability(
-                NetworkCapabilities.NET_CAPABILITY_INTERNET).addTransportType(transport).build();
-        final ConnectivityManager.NetworkCallback cb = new ConnectivityManager.NetworkCallback() {
-            @Override
-            public void onAvailable(@NonNull Network network) {
-                final long startTime = SystemClock.elapsedRealtime();
-                try {
-                    // Since history of network stats only have 2 hours of resolution, when it is
-                    // being queried, service will assume that history network stats has uniform
-                    // distribution and return a fraction of network stats that is originally
-                    // subject to 2 hours. To be specific:
-                    //    <returned network stats> = <total network stats> * <duration> / 2 hour,
-                    // assuming the duration can fit in a 2 hours bucket.
-                    // In the other hand, in statsd, the network stats is queried since boot,
-                    // that means in order to assert non-zero packet counts, either the test should
-                    // be run after enough time since boot, or the packet counts generated here
-                    // should be enough. That is to say:
-                    //   <total packet counts> * <up time> / 2 hour >= 1,
-                    // or
-                    //   iterations >= 2 hour / (<up time> * <packets per iteration>)
-                    // Thus, iterations can be chosen based on the factors above to make this
-                    // function generate enough packets in each direction to accommodate enough
-                    // packet counts for a fraction of history bucket.
-                    final double iterations = (TimeUnit.HOURS.toMillis(2) / startTime / 7);
-                    // While just enough iterations are going to make the test flaky, add a 20%
-                    // buffer to stabilize it and make sure it's in a reasonable range, so it won't
-                    // consumes more than 100kb of traffic, or generates 0 byte of traffic.
-                    final int augmentedIterations =
-                            (int) Math.max(iterations * 1.2, MIN_EXERCISE_HOST_ITERATIONS);
-                    if (augmentedIterations > MAX_EXERCISE_HOST_ITERATIONS) {
-                        throw new IllegalStateException("Exceeded max allowed iterations"
-                                + ", iterations=" + augmentedIterations
-                                + ", uptime=" + TimeUnit.MILLISECONDS.toSeconds(startTime) + "s");
-                    }
-
-                    for (int i = 0; i < augmentedIterations; i++) {
-                        // By observing results of "dumpsys netstats --uid", typically the single
-                        // run of the https request below generates 4200/1080 rx/tx bytes with
-                        // around 7/9 rx/tx packets.
-                        // This blocks the thread of NetworkCallback, thus no other event
-                        // can be processed before return.
-                        exerciseRemoteHost(cm, network, new URL(HTTPS_HOST_URL));
-                    }
-                    Log.i(TAG, "exerciseRemoteHost successful in " + (SystemClock.elapsedRealtime()
-                            - startTime) + " ms with iterations=" + augmentedIterations
-                            + ", uptime=" + TimeUnit.MILLISECONDS.toSeconds(startTime) + "s");
-                } catch (Exception e) {
-                    Log.e(TAG, "exerciseRemoteHost failed in " + (SystemClock.elapsedRealtime()
-                            - startTime) + " ms: " + e);
-                } finally {
-                    cm.unregisterNetworkCallback(this);
-                    finish();
-                }
-            }
-        };
-
-        // Request network, and make http query when the network is available.
-        cm.requestNetwork(request, cb);
-    }
-
-    /**
-     * Generate traffic on specified network.
-     */
-    private void exerciseRemoteHost(@NonNull ConnectivityManager cm, @NonNull Network network,
-            @NonNull URL url) throws Exception {
-        cm.bindProcessToNetwork(network);
-        HttpURLConnection urlc = null;
-        try {
-            urlc = (HttpURLConnection) network.openConnection(url);
-            urlc.setConnectTimeout(NETWORK_TIMEOUT_MILLIS);
-            urlc.setUseCaches(false);
-            urlc.connect();
-        } finally {
-            if (urlc != null) {
-                urlc.disconnect();
-            }
-        }
-    }
-
     // Trigger force poll on NetworkStatsService to make sure the service get most updated network
     // stats from lower layer on subsequent verifications.
     private void doPollNetworkStats() {
diff --git a/tests/src/android/cts/statsd/atom/BaseTestCase.java b/tests/src/android/cts/statsd/atom/BaseTestCase.java
index 6429570..a074c95 100644
--- a/tests/src/android/cts/statsd/atom/BaseTestCase.java
+++ b/tests/src/android/cts/statsd/atom/BaseTestCase.java
@@ -133,9 +133,11 @@
      * @param pkgName Test package name, such as "com.android.server.cts.netstats".
      * @param testClassName Test class name; either a fully qualified name, or "." + a class name.
      * @param testMethodName Test method name.
+     * @return {@link TestRunResult} of this invocation.
      * @throws DeviceNotAvailableException
      */
-    protected void runDeviceTests(@Nonnull String pkgName,
+    @Nonnull
+    protected TestRunResult runDeviceTests(@Nonnull String pkgName,
             @Nullable String testClassName, @Nullable String testMethodName)
             throws DeviceNotAvailableException {
         if (testClassName != null && testClassName.startsWith(".")) {
@@ -175,6 +177,8 @@
             }
             throw new AssertionError(errorBuilder.toString());
         }
+
+        return result;
     }
 
     protected boolean statsdDisabled() throws DeviceNotAvailableException {
diff --git a/tests/src/android/cts/statsd/atom/UidAtomTests.java b/tests/src/android/cts/statsd/atom/UidAtomTests.java
index c366c58..012803d 100644
--- a/tests/src/android/cts/statsd/atom/UidAtomTests.java
+++ b/tests/src/android/cts/statsd/atom/UidAtomTests.java
@@ -1961,7 +1961,7 @@
         Thread.sleep(WAIT_TIME_SHORT);
 
         // Generate some traffic on mobile network.
-        runActivity("StatsdCtsForegroundActivity", "action", "action.generate_mobile_traffic");
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testGenerateMobileTraffic");
         Thread.sleep(WAIT_TIME_SHORT);
 
         // Force polling NetworkStatsService to get most updated network stats from lower layer.
@@ -1973,6 +1973,7 @@
         Thread.sleep(WAIT_TIME_SHORT);
 
         final List<Atom> atoms = getGaugeMetricDataList();
+
         assertThat(atoms.size()).isAtLeast(1);
 
         boolean foundAppStats = false;