Merge "Add ConnectivityCoverageTests to postsubmit"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index e7932fb..0ce43cc 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -49,11 +49,6 @@
   ],
   "imports": [
     {
-      "path": "packages/modules/NetworkStack"
-    }
-  ],
-  "imports": [
-    {
       "path": "frameworks/base/core/java/android/net"
     },
     {
diff --git a/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java b/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java
new file mode 100644
index 0000000..a54fd64
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts;
+
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.testutils.MiscAsserts.assertThrows;
+import static com.android.testutils.TestPermissionUtil.runAsShell;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.cts.util.CtsNetUtils;
+import android.os.BatteryStatsManager;
+import android.os.Build;
+import android.os.connectivity.CellularBatteryStats;
+import android.os.connectivity.WifiBatteryStats;
+import android.util.Log;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.SkipPresubmit;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+/**
+ * Test for BatteryStatsManager.
+ */
+@RunWith(AndroidJUnit4.class)
+public class BatteryStatsManagerTest{
+    @Rule
+    public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+    private static final String TAG = BatteryStatsManagerTest.class.getSimpleName();
+    private static final String TEST_URL = "https://connectivitycheck.gstatic.com/generate_204";
+    // This value should be the same as BatteryStatsManager.BATTERY_STATUS_DISCHARGING.
+    // TODO: Use the constant once it's available in all branches
+    private static final int BATTERY_STATUS_DISCHARGING = 3;
+
+    private Context mContext;
+    private BatteryStatsManager mBsm;
+    private ConnectivityManager mCm;
+    private CtsNetUtils mCtsNetUtils;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = getContext();
+        mBsm = mContext.getSystemService(BatteryStatsManager.class);
+        mCm = mContext.getSystemService(ConnectivityManager.class);
+        mCtsNetUtils = new CtsNetUtils(mContext);
+    }
+
+    @Test
+    @SkipPresubmit(reason = "Virtual hardware does not support wifi battery stats")
+    public void testReportNetworkInterfaceForTransports() throws Exception {
+        try {
+            final Network cellNetwork = mCtsNetUtils.connectToCell();
+            final URL url = new URL(TEST_URL);
+
+            // Make sure wifi is disabled.
+            mCtsNetUtils.ensureWifiDisconnected(null /* wifiNetworkToCheck */);
+            // Simulate the device being unplugged from charging.
+            executeShellCommand("dumpsys battery unplug");
+            executeShellCommand("dumpsys battery set status " + BATTERY_STATUS_DISCHARGING);
+            executeShellCommand("dumpsys batterystats enable pretend-screen-off");
+
+            // Get cellular battery stats
+            CellularBatteryStats cellularStatsBefore = runAsShell(UPDATE_DEVICE_STATS,
+                    mBsm::getCellularBatteryStats);
+
+            // Generate traffic on cellular network.
+            generateNetworkTraffic(cellNetwork, url);
+
+            // The mobile battery stats are updated when a network stops being the default network.
+            // ConnectivityService will call BatteryStatsManager.reportMobileRadioPowerState when
+            // removing data activity tracking.
+            final Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
+
+            // Check cellular battery stats are updated.
+            runAsShell(UPDATE_DEVICE_STATS,
+                    () -> assertStatsEventually(mBsm::getCellularBatteryStats,
+                        cellularStatsAfter -> cellularBatteryStatsIncreased(
+                        cellularStatsBefore, cellularStatsAfter)));
+
+            WifiBatteryStats wifiStatsBefore = runAsShell(UPDATE_DEVICE_STATS,
+                    mBsm::getWifiBatteryStats);
+
+            // Generate traffic on wifi network.
+            generateNetworkTraffic(wifiNetwork, url);
+            // Wifi battery stats are updated when wifi on.
+            mCtsNetUtils.toggleWifi();
+
+            // Check wifi battery stats are updated.
+            runAsShell(UPDATE_DEVICE_STATS,
+                    () -> assertStatsEventually(mBsm::getWifiBatteryStats,
+                        wifiStatsAfter -> wifiBatteryStatsIncreased(wifiStatsBefore,
+                        wifiStatsAfter)));
+        } finally {
+            // Reset battery settings.
+            executeShellCommand("dumpsys battery reset");
+            executeShellCommand("dumpsys batterystats disable pretend-screen-off");
+        }
+    }
+
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test
+    public void testReportNetworkInterfaceForTransports_throwsSecurityException()
+            throws Exception {
+        Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
+        final String iface = mCm.getLinkProperties(wifiNetwork).getInterfaceName();
+        final int[] transportType = mCm.getNetworkCapabilities(wifiNetwork).getTransportTypes();
+        assertThrows(SecurityException.class,
+                () -> mBsm.reportNetworkInterfaceForTransports(iface, transportType));
+    }
+
+    private void generateNetworkTraffic(Network network, URL url) throws IOException {
+        HttpURLConnection connection = null;
+        try {
+            connection = (HttpURLConnection) network.openConnection(url);
+            assertEquals(204, connection.getResponseCode());
+        } catch (IOException e) {
+            Log.e(TAG, "Generate traffic failed with exception " + e);
+        } finally {
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+    }
+
+    private static <T> void assertStatsEventually(Supplier<T> statsGetter,
+            Predicate<T> statsChecker) throws Exception {
+        // Wait for updating mobile/wifi stats, and check stats every 10ms.
+        final int maxTries = 1000;
+        T result = null;
+        for (int i = 1; i <= maxTries; i++) {
+            result = statsGetter.get();
+            if (statsChecker.test(result)) return;
+            Thread.sleep(10);
+        }
+        final String stats = result instanceof CellularBatteryStats
+                ? "Cellular" : "Wifi";
+        fail(stats + " battery stats did not increase.");
+    }
+
+    private static boolean cellularBatteryStatsIncreased(CellularBatteryStats before,
+            CellularBatteryStats after) {
+        return (after.getNumBytesTx() > before.getNumBytesTx())
+                && (after.getNumBytesRx() > before.getNumBytesRx())
+                && (after.getNumPacketsTx() > before.getNumPacketsTx())
+                && (after.getNumPacketsRx() > before.getNumPacketsRx());
+    }
+
+    private static boolean wifiBatteryStatsIncreased(WifiBatteryStats before,
+            WifiBatteryStats after) {
+        return (after.getNumBytesTx() > before.getNumBytesTx())
+                && (after.getNumBytesRx() > before.getNumBytesRx())
+                && (after.getNumPacketsTx() > before.getNumPacketsTx())
+                && (after.getNumPacketsRx() > before.getNumPacketsRx());
+    }
+
+    private static String executeShellCommand(String command) {
+        final String result = runShellCommand(command).trim();
+        Log.d(TAG, "Output of '" + command + "': '" + result + "'");
+        return result;
+    }
+}
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index f43c1c3..3220565 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -115,11 +115,15 @@
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
+import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkInfo.State;
+import android.net.NetworkProvider;
 import android.net.NetworkRequest;
+import android.net.NetworkScore;
 import android.net.NetworkSpecifier;
 import android.net.NetworkStateSnapshot;
 import android.net.NetworkUtils;
@@ -206,6 +210,8 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -244,6 +250,9 @@
     // Airplane Mode BroadcastReceiver Timeout
     private static final long AIRPLANE_MODE_CHANGE_TIMEOUT_MS = 10_000L;
 
+    // Timeout for applying uids allowed on restricted networks
+    private static final long APPLYING_UIDS_ALLOWED_ON_RESTRICTED_NETWORKS_TIMEOUT_MS = 3_000L;
+
     // Minimum supported keepalive counts for wifi and cellular.
     public static final int MIN_SUPPORTED_CELLULAR_KEEPALIVE_COUNT = 1;
     public static final int MIN_SUPPORTED_WIFI_KEEPALIVE_COUNT = 3;
@@ -2513,8 +2522,7 @@
         } finally {
             resetValidationConfig();
             // Reconnect wifi to reset the wifi status
-            mCtsNetUtils.ensureWifiDisconnected(null /* wifiNetworkToCheck */);
-            mCtsNetUtils.ensureWifiConnected();
+            reconnectWifi();
         }
     }
 
@@ -2589,6 +2597,88 @@
         }
     }
 
+    @AppModeFull(reason = "WRITE_DEVICE_CONFIG permission can't be granted to instant apps")
+    @Test
+    public void testSetAvoidUnvalidated() throws Exception {
+        assumeTrue(TestUtils.shouldTestSApis());
+        // TODO: Allow in debuggable ROM only. To be replaced by FabricatedOverlay
+        assumeTrue(Build.isDebuggable());
+        final boolean canRunTest = mPackageManager.hasSystemFeature(FEATURE_WIFI)
+                && mPackageManager.hasSystemFeature(FEATURE_TELEPHONY);
+        assumeTrue("testSetAvoidUnvalidated cannot execute"
+                + " unless device supports WiFi and telephony", canRunTest);
+
+        final TestableNetworkCallback wifiCb = new TestableNetworkCallback();
+        final TestableNetworkCallback defaultCb = new TestableNetworkCallback();
+        final int previousAvoidBadWifi =
+                ConnectivitySettingsManager.getNetworkAvoidBadWifi(mContext);
+
+        allowBadWifi();
+
+        final Network cellNetwork = mCtsNetUtils.connectToCell();
+        final Network wifiNetwork = prepareValidatedNetwork();
+
+        mCm.registerDefaultNetworkCallback(defaultCb);
+        mCm.registerNetworkCallback(makeWifiNetworkRequest(), wifiCb);
+
+        try {
+            // Verify wifi is the default network.
+            defaultCb.eventuallyExpect(CallbackEntry.AVAILABLE, NETWORK_CALLBACK_TIMEOUT_MS,
+                    entry -> wifiNetwork.equals(entry.getNetwork()));
+            wifiCb.eventuallyExpect(CallbackEntry.AVAILABLE, NETWORK_CALLBACK_TIMEOUT_MS,
+                    entry -> wifiNetwork.equals(entry.getNetwork()));
+            assertTrue(mCm.getNetworkCapabilities(wifiNetwork).hasCapability(
+                    NET_CAPABILITY_VALIDATED));
+
+            // Configure response code for unvalidated network
+            configTestServer(Status.INTERNAL_ERROR, Status.INTERNAL_ERROR);
+            mCm.reportNetworkConnectivity(wifiNetwork, false);
+            // Default network should stay on unvalidated wifi because avoid bad wifi is disabled.
+            defaultCb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED,
+                    NETWORK_CALLBACK_TIMEOUT_MS,
+                    entry -> !((CallbackEntry.CapabilitiesChanged) entry).getCaps()
+                            .hasCapability(NET_CAPABILITY_VALIDATED));
+            wifiCb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED,
+                    NETWORK_CALLBACK_TIMEOUT_MS,
+                    entry -> !((CallbackEntry.CapabilitiesChanged) entry).getCaps()
+                            .hasCapability(NET_CAPABILITY_VALIDATED));
+
+            runAsShell(NETWORK_SETTINGS, () -> {
+                mCm.setAvoidUnvalidated(wifiNetwork);
+            });
+            // Default network should be updated to validated cellular network.
+            defaultCb.eventuallyExpect(CallbackEntry.AVAILABLE, NETWORK_CALLBACK_TIMEOUT_MS,
+                    entry -> cellNetwork.equals(entry.getNetwork()));
+            // No update on wifi callback.
+            wifiCb.assertNoCallback();
+        } finally {
+            mCm.unregisterNetworkCallback(wifiCb);
+            mCm.unregisterNetworkCallback(defaultCb);
+            resetAvoidBadWifi(previousAvoidBadWifi);
+            resetValidationConfig();
+            // Reconnect wifi to reset the wifi status
+            reconnectWifi();
+        }
+    }
+
+    private void resetAvoidBadWifi(int settingValue) {
+        setTestAllowBadWifiResource(0 /* timeMs */);
+        ConnectivitySettingsManager.setNetworkAvoidBadWifi(mContext, settingValue);
+    }
+
+    private void allowBadWifi() {
+        setTestAllowBadWifiResource(
+                System.currentTimeMillis() + WIFI_CONNECT_TIMEOUT_MS /* timeMs */);
+        ConnectivitySettingsManager.setNetworkAvoidBadWifi(mContext,
+                ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_IGNORE);
+    }
+
+    private void setTestAllowBadWifiResource(long timeMs) {
+        runAsShell(NETWORK_SETTINGS, () -> {
+            mCm.setTestAllowBadWifiUntil(timeMs);
+        });
+    }
+
     private Network expectNetworkHasCapability(Network network, int expectedNetCap, long timeout)
             throws Exception {
         final CompletableFuture<Network> future = new CompletableFuture();
@@ -2628,6 +2718,21 @@
         mHttpServer.start();
     }
 
+    private Network reconnectWifi() {
+        mCtsNetUtils.ensureWifiDisconnected(null /* wifiNetworkToCheck */);
+        return mCtsNetUtils.ensureWifiConnected();
+    }
+
+    private Network prepareValidatedNetwork() throws Exception {
+        prepareHttpServer();
+        configTestServer(Status.NO_CONTENT, Status.NO_CONTENT);
+        // Disconnect wifi first then start wifi network with configuration.
+        final Network wifiNetwork = reconnectWifi();
+
+        return expectNetworkHasCapability(wifiNetwork, NET_CAPABILITY_VALIDATED,
+                WIFI_CONNECT_TIMEOUT_MS);
+    }
+
     private Network preparePartialConnectivity() throws Exception {
         prepareHttpServer();
         // Configure response code for partial connectivity
@@ -2747,4 +2852,110 @@
                     mContext, mobileDataPreferredUids);
         }
     }
+
+    /** Wait for assigned time. */
+    private void waitForMs(long ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (InterruptedException e) {
+            fail("Thread was interrupted");
+        }
+    }
+
+    private void assertBindSocketToNetworkSuccess(final Network network) throws Exception {
+        final CompletableFuture<Boolean> future = new CompletableFuture<>();
+        final ExecutorService executor = Executors.newSingleThreadExecutor();
+        try {
+            executor.execute(() -> {
+                for (int i = 0; i < 30; i++) {
+                    waitForMs(100);
+
+                    try (Socket socket = new Socket()) {
+                        network.bindSocket(socket);
+                        future.complete(true);
+                        return;
+                    } catch (IOException e) { }
+                }
+            });
+            assertTrue(future.get(APPLYING_UIDS_ALLOWED_ON_RESTRICTED_NETWORKS_TIMEOUT_MS,
+                    TimeUnit.MILLISECONDS));
+        } finally {
+            executor.shutdown();
+        }
+    }
+
+    @AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
+    @Test
+    public void testUidsAllowedOnRestrictedNetworks() throws Exception {
+        assumeTrue(TestUtils.shouldTestSApis());
+
+        final int uid = mPackageManager.getPackageUid(mContext.getPackageName(), 0 /* flag */);
+        final Set<Integer> originalUidsAllowedOnRestrictedNetworks =
+                ConnectivitySettingsManager.getUidsAllowedOnRestrictedNetworks(mContext);
+        // CtsNetTestCases uid should not list in UIDS_ALLOWED_ON_RESTRICTED_NETWORKS setting
+        // because it has been just installed to device. In case the uid is existed in setting
+        // mistakenly, try to remove the uid and set correct uids to setting.
+        originalUidsAllowedOnRestrictedNetworks.remove(uid);
+        ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks(mContext,
+                originalUidsAllowedOnRestrictedNetworks);
+
+        final Handler h = new Handler(Looper.getMainLooper());
+        final TestableNetworkCallback testNetworkCb = new TestableNetworkCallback();
+        mCm.registerBestMatchingNetworkCallback(new NetworkRequest.Builder().clearCapabilities()
+                .addTransportType(NetworkCapabilities.TRANSPORT_TEST).build(), testNetworkCb, h);
+
+        // Create test network agent with restricted network.
+        final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .build();
+        final NetworkScore score = new NetworkScore.Builder()
+                .setExiting(false)
+                .setTransportPrimary(false)
+                .setKeepConnectedReason(NetworkScore.KEEP_CONNECTED_FOR_HANDOVER)
+                .build();
+        final NetworkAgent agent = new NetworkAgent(mContext, Looper.getMainLooper(),
+                TAG, nc, new LinkProperties(), score, new NetworkAgentConfig.Builder().build(),
+                new NetworkProvider(mContext, Looper.getMainLooper(), TAG)) {};
+        runWithShellPermissionIdentity(() -> agent.register(),
+                android.Manifest.permission.MANAGE_TEST_NETWORKS);
+        agent.markConnected();
+
+        final Network network = agent.getNetwork();
+
+        try (Socket socket = new Socket()) {
+            testNetworkCb.eventuallyExpect(CallbackEntry.AVAILABLE, NETWORK_CALLBACK_TIMEOUT_MS,
+                    entry -> network.equals(entry.getNetwork()));
+            // Verify that the network is restricted.
+            final NetworkCapabilities testNetworkNc = mCm.getNetworkCapabilities(network);
+            assertNotNull(testNetworkNc);
+            assertFalse(testNetworkNc.hasCapability(
+                    NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED));
+            // CtsNetTestCases package doesn't hold CONNECTIVITY_USE_RESTRICTED_NETWORKS, so it
+            // does not allow to bind socket to restricted network.
+            assertThrows(IOException.class, () -> network.bindSocket(socket));
+
+            // Add CtsNetTestCases uid to UIDS_ALLOWED_ON_RESTRICTED_NETWORKS setting, then it can
+            // bind socket to restricted network normally.
+            final Set<Integer> newUidsAllowedOnRestrictedNetworks =
+                    new ArraySet<>(originalUidsAllowedOnRestrictedNetworks);
+            newUidsAllowedOnRestrictedNetworks.add(uid);
+            ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks(mContext,
+                    newUidsAllowedOnRestrictedNetworks);
+            // Wait a while for sending allowed uids on the restricted network to netd.
+            // TODD: Have a significant signal to know the uids has been send to netd.
+            assertBindSocketToNetworkSuccess(network);
+        } finally {
+            mCm.unregisterNetworkCallback(testNetworkCb);
+            agent.unregister();
+
+            // Restore setting.
+            ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks(mContext,
+                    originalUidsAllowedOnRestrictedNetworks);
+        }
+    }
 }