Merge "@RestoreDefaultNetwork DnsTest" into main
diff --git a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
index 136dfb1..ac4d8b1 100644
--- a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
+++ b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
@@ -22,6 +22,12 @@
import static android.net.NetworkCapabilities.TRANSPORT_LOWPAN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
+import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
+import static android.net.NetworkStats.METERED_YES;
+import static android.net.NetworkTemplate.MATCH_BLUETOOTH;
+import static android.net.NetworkTemplate.MATCH_ETHERNET;
+import static android.net.NetworkTemplate.MATCH_MOBILE;
+import static android.net.NetworkTemplate.MATCH_WIFI;
import static android.net.TetheringManager.TETHERING_BLUETOOTH;
import static android.net.TetheringManager.TETHERING_ETHERNET;
import static android.net.TetheringManager.TETHERING_NCM;
@@ -48,6 +54,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.net.NetworkCapabilities;
+import android.net.NetworkTemplate;
import android.stats.connectivity.DownstreamType;
import android.stats.connectivity.ErrorCode;
import android.stats.connectivity.UpstreamType;
@@ -419,4 +426,46 @@
}
return false;
}
+
+ /**
+ * Build NetworkTemplate for the given upstream type.
+ *
+ * <p> NetworkTemplate.Builder API was introduced in Android T.
+ *
+ * @param type the upstream type
+ * @return A NetworkTemplate object with a corresponding match rule or null if tethering
+ * metrics' data usage cannot be collected for a given upstream type.
+ */
+ @Nullable
+ public static NetworkTemplate buildNetworkTemplateForUpstreamType(@NonNull UpstreamType type) {
+ if (!isUsageSupportedForUpstreamType(type)) return null;
+
+ switch (type) {
+ case UT_CELLULAR:
+ // TODO: Handle the DUN connection, which is not a default network.
+ return new NetworkTemplate.Builder(MATCH_MOBILE)
+ .setMeteredness(METERED_YES)
+ .setDefaultNetworkStatus(DEFAULT_NETWORK_YES)
+ .build();
+ case UT_WIFI:
+ return new NetworkTemplate.Builder(MATCH_WIFI)
+ .setMeteredness(METERED_YES)
+ .setDefaultNetworkStatus(DEFAULT_NETWORK_YES)
+ .build();
+ case UT_BLUETOOTH:
+ return new NetworkTemplate.Builder(MATCH_BLUETOOTH)
+ .setMeteredness(METERED_YES)
+ .setDefaultNetworkStatus(DEFAULT_NETWORK_YES)
+ .build();
+ case UT_ETHERNET:
+ return new NetworkTemplate.Builder(MATCH_ETHERNET)
+ .setMeteredness(METERED_YES)
+ .setDefaultNetworkStatus(DEFAULT_NETWORK_YES)
+ .build();
+ default:
+ Log.e(TAG, "Unsupported UpstreamType: " + type.name());
+ break;
+ }
+ return null;
+ }
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
index 7cef9cb..fbc2893 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
@@ -22,6 +22,10 @@
import static android.net.NetworkCapabilities.TRANSPORT_LOWPAN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
+import static android.net.NetworkTemplate.MATCH_BLUETOOTH;
+import static android.net.NetworkTemplate.MATCH_ETHERNET;
+import static android.net.NetworkTemplate.MATCH_MOBILE;
+import static android.net.NetworkTemplate.MATCH_WIFI;
import static android.net.TetheringManager.TETHERING_BLUETOOTH;
import static android.net.TetheringManager.TETHERING_ETHERNET;
import static android.net.TetheringManager.TETHERING_NCM;
@@ -47,11 +51,14 @@
import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.net.NetworkCapabilities;
+import android.net.NetworkTemplate;
+import android.os.Build;
import android.stats.connectivity.DownstreamType;
import android.stats.connectivity.ErrorCode;
import android.stats.connectivity.UpstreamType;
@@ -62,8 +69,11 @@
import com.android.networkstack.tethering.UpstreamNetworkState;
import com.android.networkstack.tethering.metrics.TetheringMetrics.Dependencies;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -72,12 +82,15 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public final class TetheringMetricsTest {
+ @Rule public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
private static final String TEST_CALLER_PKG = "com.test.caller.pkg";
private static final String SETTINGS_PKG = "com.android.settings";
private static final String SYSTEMUI_PKG = "com.android.systemui";
private static final String GMS_PKG = "com.google.android.gms";
private static final long TEST_START_TIME = 1670395936033L;
private static final long SECOND_IN_MILLIS = 1_000L;
+ private static final int MATCH_NONE = -1;
@Mock private Context mContext;
@Mock private Dependencies mDeps;
@@ -392,4 +405,27 @@
runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_LOWPAN, false /* isSupported */);
runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_UNKNOWN, false /* isSupported */);
}
+
+ private void runBuildNetworkTemplateForUpstreamType(final UpstreamType upstreamType,
+ final int matchRule) {
+ final NetworkTemplate template =
+ TetheringMetrics.buildNetworkTemplateForUpstreamType(upstreamType);
+ if (matchRule == MATCH_NONE) {
+ assertNull(template);
+ } else {
+ assertEquals(matchRule, template.getMatchRule());
+ }
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testBuildNetworkTemplateForUpstreamType() {
+ runBuildNetworkTemplateForUpstreamType(UpstreamType.UT_CELLULAR, MATCH_MOBILE);
+ runBuildNetworkTemplateForUpstreamType(UpstreamType.UT_WIFI, MATCH_WIFI);
+ runBuildNetworkTemplateForUpstreamType(UpstreamType.UT_BLUETOOTH, MATCH_BLUETOOTH);
+ runBuildNetworkTemplateForUpstreamType(UpstreamType.UT_ETHERNET, MATCH_ETHERNET);
+ runBuildNetworkTemplateForUpstreamType(UpstreamType.UT_WIFI_AWARE, MATCH_NONE);
+ runBuildNetworkTemplateForUpstreamType(UpstreamType.UT_LOWPAN, MATCH_NONE);
+ runBuildNetworkTemplateForUpstreamType(UpstreamType.UT_UNKNOWN, MATCH_NONE);
+ }
}
diff --git a/networksecurity/service/Android.bp b/networksecurity/service/Android.bp
index 66d201a..e33abd5 100644
--- a/networksecurity/service/Android.bp
+++ b/networksecurity/service/Android.bp
@@ -27,6 +27,7 @@
],
libs: [
+ "framework-configinfrastructure",
"framework-connectivity-pre-jarjar",
"service-connectivity-pre-jarjar",
],
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java
new file mode 100644
index 0000000..8dd5951
--- /dev/null
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 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 com.android.server.net.ct;
+
+import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
+
+import android.content.Context;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.Properties;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.modules.utils.build.SdkLevel;
+
+import java.util.concurrent.Executors;
+
+/** Listener class for the Certificate Transparency Phenotype flags. */
+class CertificateTransparencyFlagsListener implements DeviceConfig.OnPropertiesChangedListener {
+
+ private static final String TAG = "CertificateTransparency";
+
+ private static final String VERSION = "version";
+ private static final String CONTENT_URL = "content_url";
+ private static final String METADATA_URL = "metadata_url";
+
+ CertificateTransparencyFlagsListener(Context context) {}
+
+ void initialize() {
+ DeviceConfig.addOnPropertiesChangedListener(
+ NAMESPACE_TETHERING, Executors.newSingleThreadExecutor(), this);
+ // TODO: handle property changes triggering on boot before registering this listener.
+ }
+
+ @Override
+ public void onPropertiesChanged(Properties properties) {
+ if (!SdkLevel.isAtLeastV() || !NAMESPACE_TETHERING.equals(properties.getNamespace())) {
+ return;
+ }
+
+ String newVersion = DeviceConfig.getString(NAMESPACE_TETHERING, VERSION, "");
+ String newContentUrl = DeviceConfig.getString(NAMESPACE_TETHERING, CONTENT_URL, "");
+ String newMetadataUrl = DeviceConfig.getString(NAMESPACE_TETHERING, METADATA_URL, "");
+ if (TextUtils.isEmpty(newVersion)
+ || TextUtils.isEmpty(newContentUrl)
+ || TextUtils.isEmpty(newMetadataUrl)) {
+ return;
+ }
+
+ Log.d(TAG, "newVersion=" + newVersion);
+ Log.d(TAG, "newContentUrl=" + newContentUrl);
+ Log.d(TAG, "newMetadataUrl=" + newMetadataUrl);
+ // TODO: start download of URLs.
+ }
+}
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
index 8c53bf7..406a57f 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyService.java
@@ -23,6 +23,7 @@
import com.android.net.ct.flags.Flags;
import com.android.net.module.util.DeviceConfigUtils;
+import com.android.server.SystemService;
/** Implementation of the Certificate Transparency service. */
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@@ -32,6 +33,8 @@
private static final String CERTIFICATE_TRANSPARENCY_ENABLED =
"certificate_transparency_service_enabled";
+ private final CertificateTransparencyFlagsListener mFlagsListener;
+
/**
* @return true if the CertificateTransparency service is enabled.
*/
@@ -43,7 +46,9 @@
}
/** Creates a new {@link CertificateTransparencyService} object. */
- public CertificateTransparencyService(Context context) {}
+ public CertificateTransparencyService(Context context) {
+ mFlagsListener = new CertificateTransparencyFlagsListener(context);
+ }
/**
* Called by {@link com.android.server.ConnectivityServiceInitializer}.
@@ -51,6 +56,13 @@
* @see com.android.server.SystemService#onBootPhase
*/
public void onBootPhase(int phase) {
- Log.d(TAG, "CertificateTransparencyService#onBootPhase " + phase);
+
+ switch (phase) {
+ case SystemService.PHASE_BOOT_COMPLETED:
+ Log.d(TAG, "setting up flags listeners");
+ mFlagsListener.initialize();
+ break;
+ default:
+ }
}
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
index 8123d27..7fa605a 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
@@ -390,12 +390,18 @@
*/
public void dump(PrintWriter pw) {
discoveryExecutor.checkAndRunOnHandlerThread(() -> {
- pw.println();
+ pw.println("Clients:");
// Dump ServiceTypeClients
for (MdnsServiceTypeClient serviceTypeClient
: perSocketServiceTypeClients.getAllMdnsServiceTypeClient()) {
serviceTypeClient.dump(pw);
}
+ pw.println();
+ // Dump ServiceCache
+ pw.println("Cached services:");
+ if (serviceCache != null) {
+ serviceCache.dump(pw, " ");
+ }
});
}
}
\ No newline at end of file
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java b/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
index 3636644..2957da5 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
@@ -19,11 +19,14 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.Network;
+import android.os.SystemClock;
+import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.DnsUtils;
import java.io.IOException;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@@ -426,4 +429,18 @@
return count;
}
+
+ @Override
+ public String toString() {
+ return "Name: " + TextUtils.join(".", serviceName)
+ + ", pointerRecords: " + pointerRecords
+ + ", serviceRecord: " + serviceRecord
+ + ", textRecord: " + textRecord
+ + ", inet4AddressRecords: " + inet4AddressRecords
+ + ", inet6AddressRecords: " + inet6AddressRecords
+ + ", interfaceIndex: " + interfaceIndex
+ + ", network: " + network
+ + ", lastUpdateTime: " + Instant.now().minusMillis(
+ SystemClock.elapsedRealtime() - lastUpdateTime);
+ }
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
index a8a4ef1..591ed8b 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
@@ -32,6 +32,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.connectivity.mdns.util.MdnsUtils;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@@ -71,6 +72,11 @@
return Objects.equals(mUpperCaseServiceType, ((CacheKey) other).mUpperCaseServiceType)
&& Objects.equals(mSocketKey, ((CacheKey) other).mSocketKey);
}
+
+ @Override
+ public String toString() {
+ return "CacheKey{ ServiceType=" + mUpperCaseServiceType + ", " + mSocketKey + " }";
+ }
}
/**
* A map of cached services. Key is composed of service type and socket. Value is the list of
@@ -353,6 +359,22 @@
mNextExpirationTime = getNextExpirationTime(now);
}
+ /**
+ * Dump ServiceCache state.
+ */
+ public void dump(PrintWriter pw, String indent) {
+ ensureRunningOnHandlerThread(mHandler);
+ // IndentingPrintWriter cannot be used on the mDNS stack build. So, manually add an indent.
+ for (int i = 0; i < mCachedServices.size(); i++) {
+ final CacheKey key = mCachedServices.keyAt(i);
+ pw.println(indent + key);
+ for (MdnsResponse response : mCachedServices.valueAt(i)) {
+ pw.println(indent + " Response{ " + response + " }");
+ }
+ pw.println();
+ }
+ }
+
/*** Callbacks for listening service expiration */
public interface ServiceExpiredCallback {
/*** Notify the service is expired */
diff --git a/service/src/com/android/server/connectivity/DnsManager.java b/service/src/com/android/server/connectivity/DnsManager.java
index ac02229..b95e3b1 100644
--- a/service/src/com/android/server/connectivity/DnsManager.java
+++ b/service/src/com/android/server/connectivity/DnsManager.java
@@ -41,6 +41,7 @@
import android.net.NetworkCapabilities;
import android.net.ResolverParamsParcel;
import android.net.Uri;
+import android.net.resolv.aidl.DohParamsParcel;
import android.net.shared.PrivateDnsConfig;
import android.os.Binder;
import android.os.RemoteException;
@@ -52,16 +53,17 @@
import android.util.Pair;
import java.net.InetAddress;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Collectors;
/**
* Encapsulate the management of DNS settings for networks.
@@ -382,15 +384,14 @@
paramsParcel.domains = getDomainStrings(lp.getDomains());
paramsParcel.tlsName = strictMode ? privateDnsCfg.hostname : "";
paramsParcel.tlsServers =
- strictMode ? makeStrings(
- Arrays.stream(privateDnsCfg.ips)
- .filter((ip) -> lp.isReachable(ip))
- .collect(Collectors.toList()))
+ strictMode ? makeStrings(getReachableAddressList(privateDnsCfg.ips, lp))
: useTls ? paramsParcel.servers // Opportunistic
: new String[0]; // Off
paramsParcel.transportTypes = nc.getTransportTypes();
paramsParcel.meteredNetwork = nc.isMetered();
paramsParcel.interfaceNames = lp.getAllInterfaceNames().toArray(new String[0]);
+ paramsParcel.dohParams = makeDohParamsParcel(privateDnsCfg, lp);
+
// Prepare to track the validation status of the DNS servers in the
// resolver config when private DNS is in opportunistic or strict mode.
if (useTls) {
@@ -404,15 +405,16 @@
}
Log.d(TAG, String.format("sendDnsConfigurationForNetwork(%d, %s, %s, %d, %d, %d, %d, "
- + "%d, %d, %s, %s, %s, %b, %s)", paramsParcel.netId,
+ + "%d, %d, %s, %s, %s, %b, %s, %s, %s, %s, %d)", paramsParcel.netId,
Arrays.toString(paramsParcel.servers), Arrays.toString(paramsParcel.domains),
paramsParcel.sampleValiditySeconds, paramsParcel.successThreshold,
paramsParcel.minSamples, paramsParcel.maxSamples, paramsParcel.baseTimeoutMsec,
paramsParcel.retryCount, paramsParcel.tlsName,
Arrays.toString(paramsParcel.tlsServers),
Arrays.toString(paramsParcel.transportTypes), paramsParcel.meteredNetwork,
- Arrays.toString(paramsParcel.interfaceNames)));
-
+ Arrays.toString(paramsParcel.interfaceNames),
+ paramsParcel.dohParams.name, Arrays.toString(paramsParcel.dohParams.ips),
+ paramsParcel.dohParams.dohpath, paramsParcel.dohParams.port));
try {
mDnsResolver.setResolverConfiguration(paramsParcel);
} catch (RemoteException | ServiceSpecificException e) {
@@ -498,4 +500,26 @@
private static String[] getDomainStrings(String domains) {
return (TextUtils.isEmpty(domains)) ? new String[0] : domains.split(" ");
}
+
+ @NonNull
+ private List<InetAddress> getReachableAddressList(@NonNull InetAddress[] ips,
+ @NonNull LinkProperties lp) {
+ final ArrayList<InetAddress> out = new ArrayList<InetAddress>(Arrays.asList(ips));
+ out.removeIf(ip -> !lp.isReachable(ip));
+ return out;
+ }
+
+ @NonNull
+ private DohParamsParcel makeDohParamsParcel(@NonNull PrivateDnsConfig cfg,
+ @NonNull LinkProperties lp) {
+ if (cfg.mode == PRIVATE_DNS_MODE_OFF) {
+ return new DohParamsParcel.Builder().build();
+ }
+ return new DohParamsParcel.Builder()
+ .setName(cfg.dohName)
+ .setIps(makeStrings(getReachableAddressList(cfg.dohIps, lp)))
+ .setDohpath(cfg.dohPath)
+ .setPort(cfg.dohPort)
+ .build();
+ }
}
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index ed0670d..94a2411 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -296,6 +296,9 @@
"framework-connectivity-t.stubs.module_lib",
"framework-location.stubs.module_lib",
],
+ static_libs: [
+ "modules-utils-build",
+ ],
jarjar_rules: "jarjar-rules-shared.txt",
visibility: [
"//cts/tests/tests/net",
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStatsUtils.java b/staticlibs/framework/com/android/net/module/util/NetworkStatsUtils.java
index 41a9428..04b6174 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStatsUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStatsUtils.java
@@ -19,6 +19,9 @@
import android.app.usage.NetworkStats;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
+
+import java.util.ArrayList;
/**
* Various utilities used for NetworkStats related code.
@@ -105,12 +108,21 @@
*/
public static android.net.NetworkStats fromPublicNetworkStats(
NetworkStats publiceNetworkStats) {
- android.net.NetworkStats stats = new android.net.NetworkStats(0L, 0);
+ final ArrayList<android.net.NetworkStats.Entry> entries = new ArrayList<>();
while (publiceNetworkStats.hasNextBucket()) {
NetworkStats.Bucket bucket = new NetworkStats.Bucket();
publiceNetworkStats.getNextBucket(bucket);
- final android.net.NetworkStats.Entry entry = fromBucket(bucket);
- stats = stats.addEntry(entry);
+ entries.add(fromBucket(bucket));
+ }
+ android.net.NetworkStats stats = new android.net.NetworkStats(0L, 0);
+ // The new API is only supported on devices running the mainline version of `NetworkStats`.
+ // It should always be used when available for memory efficiency.
+ if (SdkLevel.isAtLeastT()) {
+ stats = stats.addEntries(entries);
+ } else {
+ for (android.net.NetworkStats.Entry entry : entries) {
+ stats = stats.addEntry(entry);
+ }
}
return stats;
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/NetworkStatsUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkStatsUtilsTest.kt
index baadad0..97d3c19 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/NetworkStatsUtilsTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkStatsUtilsTest.kt
@@ -17,15 +17,27 @@
package com.android.net.module.util
import android.net.NetworkStats
+import android.net.NetworkStats.DEFAULT_NETWORK_NO
+import android.net.NetworkStats.DEFAULT_NETWORK_YES
+import android.net.NetworkStats.Entry
+import android.net.NetworkStats.METERED_NO
+import android.net.NetworkStats.ROAMING_NO
+import android.net.NetworkStats.ROAMING_YES
+import android.net.NetworkStats.SET_DEFAULT
+import android.net.NetworkStats.TAG_NONE
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.testutils.assertEntryEquals
+import com.android.testutils.assertNetworkStatsEquals
+import com.android.testutils.makePublicStatsFromAndroidNetStats
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
+
@RunWith(AndroidJUnit4::class)
@SmallTest
@@ -93,6 +105,37 @@
assertEntryEquals(expectedEntry, entry)
}
+ @Test
+ fun testPublicStatsToAndroidNetStats() {
+ val uid1 = 10001
+ val uid2 = 10002
+ val testIface = "wlan0"
+ val testAndroidNetStats = NetworkStats(0L, 3)
+ .addEntry(Entry(testIface, uid1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3))
+ .addEntry(Entry(
+ testIface, uid2, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 2, 7, 2, 5, 7))
+ .addEntry(Entry(testIface, uid2, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 4, 5, 3, 1, 8))
+ val publicStats: android.app.usage.NetworkStats =
+ makePublicStatsFromAndroidNetStats(testAndroidNetStats)
+ val androidNetStats: NetworkStats =
+ NetworkStatsUtils.fromPublicNetworkStats(publicStats)
+
+ // 1. The public `NetworkStats` class does not include interface information.
+ // Interface details must be removed and items with duplicated
+ // keys need to be merged before making any comparisons.
+ // 2. The public `NetworkStats` class lacks an operations field.
+ // Thus, the information will not be preserved during the conversion.
+ val expectedStats = NetworkStats(0L, 2)
+ .addEntry(Entry(null, uid1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 0))
+ .addEntry(Entry(null, uid2, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 6, 12, 5, 6, 0))
+ assertNetworkStatsEquals(expectedStats, androidNetStats)
+ }
+
private fun makeMockBucket(
uid: Int,
tag: Int,
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt
index f2b14f5..e8c7297 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt
@@ -16,7 +16,12 @@
package com.android.testutils
+import android.app.usage.NetworkStatsManager
+import android.content.Context
+import android.net.INetworkStatsService
+import android.net.INetworkStatsSession
import android.net.NetworkStats
+import android.net.NetworkTemplate
import android.text.TextUtils
import com.android.modules.utils.build.SdkLevel
import kotlin.test.assertTrue
@@ -112,3 +117,32 @@
fun assertParcelingIsLossless(stats: NetworkStats) {
assertParcelingIsLossless(stats) { a, b -> orderInsensitiveEquals(a, b) }
}
+
+/**
+ * Make a {@link android.app.usage.NetworkStats} instance from
+ * a {@link android.net.NetworkStats} instance.
+ */
+// It's not possible to directly create a mocked `NetworkStats` instance
+// because of limitations with `NetworkStats#getNextBucket`.
+// As a workaround for testing, create a mock by controlling the return values
+// from the mocked service that provides the `NetworkStats` data.
+// Notes:
+// 1. The order of records in the final `NetworkStats` object might change or
+// some records might be merged if there are items with duplicate keys.
+// 2. The interface and operations fields will be empty since there is
+// no such field in the {@link android.app.usage.NetworkStats}.
+fun makePublicStatsFromAndroidNetStats(androidNetStats: NetworkStats):
+ android.app.usage.NetworkStats {
+ val mockService = Mockito.mock(INetworkStatsService::class.java)
+ val manager = NetworkStatsManager(Mockito.mock(Context::class.java), mockService)
+ val mockStatsSession = Mockito.mock(INetworkStatsSession::class.java)
+
+ Mockito.doReturn(mockStatsSession).`when`(mockService)
+ .openSessionForUsageStats(anyInt(), any())
+ Mockito.doReturn(androidNetStats).`when`(mockStatsSession).getSummaryForAllUid(
+ any(NetworkTemplate::class.java), anyLong(), anyLong(), anyBoolean())
+ return manager.querySummary(
+ Mockito.mock(NetworkTemplate::class.java),
+ Long.MIN_VALUE, Long.MAX_VALUE
+ )
+}
diff --git a/staticlibs/testutils/host/python/wifip2p_utils.py b/staticlibs/testutils/host/python/wifip2p_utils.py
index 8b4ffa5..ef6af75 100644
--- a/staticlibs/testutils/host/python/wifip2p_utils.py
+++ b/staticlibs/testutils/host/python/wifip2p_utils.py
@@ -14,11 +14,13 @@
from mobly import asserts
from mobly.controllers import android_device
+from net_tests_utils.host.python import tether_utils
def assume_wifi_p2p_test_preconditions(
server_device: android_device, client_device: android_device
) -> None:
+ """Preconditions check for running Wi-Fi P2P test."""
server = server_device.connectivity_multi_devices_snippet
client = client_device.connectivity_multi_devices_snippet
@@ -36,10 +38,51 @@
def setup_wifi_p2p_server_and_client(
server_device: android_device, client_device: android_device
) -> None:
- """Set up the Wi-Fi P2P server and client."""
+ """Set up the Wi-Fi P2P server and client, then connect them to establish a Wi-Fi P2P connection."""
+ server = server_device.connectivity_multi_devices_snippet
+ client = client_device.connectivity_multi_devices_snippet
+
# Start Wi-Fi P2P on both server and client.
- server_device.connectivity_multi_devices_snippet.startWifiP2p()
- client_device.connectivity_multi_devices_snippet.startWifiP2p()
+ server.startWifiP2p()
+ client.startWifiP2p()
+
+ # Get the current device name
+ server_name = server.getDeviceName()
+ client_name = client.getDeviceName()
+
+ # Generate Wi-Fi P2P group passphrase with random characters.
+ group_name = "DIRECT-" + tether_utils.generate_uuid32_base64()
+ group_passphrase = tether_utils.generate_uuid32_base64()
+
+ # Server creates a Wi-Fi P2P group
+ server.createGroup(group_name, group_passphrase)
+
+ # Start Wi-Fi P2p peers discovery on both devices
+ server.startPeersDiscovery()
+ client.startPeersDiscovery()
+
+ # Ensure the target device has been discovered
+ server_address = client.ensureDeviceDiscovered(server_name)
+ client_address = server.ensureDeviceDiscovered(client_name)
+
+ # Server invites the device to the group
+ server.inviteDeviceToGroup(group_name, group_passphrase, client_address)
+
+ # Wait for a p2p connection changed intent to ensure the invitation has been
+ # received.
+ client.waitForP2pConnectionChanged(True, group_name)
+ # Accept the group invitation
+ client.acceptGroupInvitation(server_address)
+
+ # Server waits for connection request from client and accept joining
+ server.waitForPeerConnectionRequestAndAcceptJoining(client_address)
+
+ # Wait for a p2p connection changed intent to ensure joining the group
+ client.waitForP2pConnectionChanged(False, group_name)
+
+ # Ensure Wi-Fi P2P connected on both devices
+ client.ensureDeviceConnected(server_name)
+ server.ensureDeviceConnected(client_name)
def cleanup_wifi_p2p(
diff --git a/tests/cts/multidevices/connectivity_multi_devices_test.py b/tests/cts/multidevices/connectivity_multi_devices_test.py
index eceb535..416a2e8 100644
--- a/tests/cts/multidevices/connectivity_multi_devices_test.py
+++ b/tests/cts/multidevices/connectivity_multi_devices_test.py
@@ -13,6 +13,7 @@
# limitations under the License.
from net_tests_utils.host.python import mdns_utils, multi_devices_test_base, tether_utils
+from net_tests_utils.host.python import wifip2p_utils
from net_tests_utils.host.python.tether_utils import UpstreamType
@@ -68,3 +69,21 @@
tether_utils.cleanup_tethering_for_upstream_type(
self.serverDevice, UpstreamType.NONE
)
+
+ def test_mdns_via_wifip2p(self):
+ wifip2p_utils.assume_wifi_p2p_test_preconditions(
+ self.serverDevice, self.clientDevice
+ )
+ mdns_utils.assume_mdns_test_preconditions(
+ self.clientDevice, self.serverDevice
+ )
+ try:
+ wifip2p_utils.setup_wifi_p2p_server_and_client(
+ self.serverDevice, self.clientDevice
+ )
+ mdns_utils.register_mdns_service_and_discover_resolve(
+ self.clientDevice, self.serverDevice
+ )
+ finally:
+ mdns_utils.cleanup_mdns_service(self.clientDevice, self.serverDevice)
+ wifip2p_utils.cleanup_wifi_p2p(self.serverDevice, self.clientDevice)
diff --git a/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt
index e0929bb..f8c9351 100644
--- a/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt
+++ b/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt
@@ -16,13 +16,28 @@
package com.google.snippet.connectivity
+import android.Manifest.permission.MANAGE_WIFI_NETWORK_SELECTION
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.MacAddress
import android.net.wifi.WifiManager
+import android.net.wifi.p2p.WifiP2pConfig
+import android.net.wifi.p2p.WifiP2pDevice
+import android.net.wifi.p2p.WifiP2pDeviceList
+import android.net.wifi.p2p.WifiP2pGroup
import android.net.wifi.p2p.WifiP2pManager
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.net.module.util.ArrayTrackRecord
+import com.android.testutils.runAsShell
import com.google.android.mobly.snippet.Snippet
import com.google.android.mobly.snippet.rpc.Rpc
+import com.google.snippet.connectivity.Wifip2pMultiDevicesSnippet.Wifip2pIntentReceiver.IntentReceivedEvent.ConnectionChanged
+import com.google.snippet.connectivity.Wifip2pMultiDevicesSnippet.Wifip2pIntentReceiver.IntentReceivedEvent.PeersChanged
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
+import kotlin.test.assertNotNull
import kotlin.test.fail
private const val TIMEOUT_MS = 60000L
@@ -38,6 +53,35 @@
?: fail("Could not get WifiP2pManager service")
}
private lateinit var wifip2pChannel: WifiP2pManager.Channel
+ private val wifip2pIntentReceiver = Wifip2pIntentReceiver()
+
+ private class Wifip2pIntentReceiver : BroadcastReceiver() {
+ val history = ArrayTrackRecord<IntentReceivedEvent>().newReadHead()
+
+ sealed class IntentReceivedEvent {
+ abstract val intent: Intent
+ data class ConnectionChanged(override val intent: Intent) : IntentReceivedEvent()
+ data class PeersChanged(override val intent: Intent) : IntentReceivedEvent()
+ }
+
+ override fun onReceive(context: Context, intent: Intent) {
+ when (intent.action) {
+ WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {
+ history.add(ConnectionChanged(intent))
+ }
+ WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {
+ history.add(PeersChanged(intent))
+ }
+ }
+ }
+
+ inline fun <reified T : IntentReceivedEvent> eventuallyExpectedIntent(
+ timeoutMs: Long = TIMEOUT_MS,
+ crossinline predicate: (T) -> Boolean = { true }
+ ): T = history.poll(timeoutMs) { it is T && predicate(it) }.also {
+ assertNotNull(it, "Intent ${T::class} not received within ${timeoutMs}ms.")
+ } as T
+ }
@Rpc(description = "Check whether the device supports Wi-Fi P2P.")
fun isP2pSupported() = wifiManager.isP2pSupported()
@@ -55,6 +99,10 @@
}
}
p2pStateEnabledFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ // Register an intent filter to receive Wi-Fi P2P intents
+ val filter = IntentFilter(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)
+ filter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)
+ context.registerReceiver(wifip2pIntentReceiver, filter)
}
@Rpc(description = "Stop Wi-Fi P2P")
@@ -63,5 +111,202 @@
wifip2pManager.cancelConnect(wifip2pChannel, null)
wifip2pManager.removeGroup(wifip2pChannel, null)
}
+ // Unregister the intent filter
+ context.unregisterReceiver(wifip2pIntentReceiver)
+ }
+
+ @Rpc(description = "Get the current device name")
+ fun getDeviceName(): String {
+ // Retrieve current device info
+ val deviceFuture = CompletableFuture<String>()
+ wifip2pManager.requestDeviceInfo(wifip2pChannel) { wifiP2pDevice ->
+ if (wifiP2pDevice != null) {
+ deviceFuture.complete(wifiP2pDevice.deviceName)
+ }
+ }
+ // Return current device name
+ return deviceFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ }
+
+ @Rpc(description = "Wait for a p2p connection changed intent and check the group")
+ @Suppress("DEPRECATION")
+ fun waitForP2pConnectionChanged(ignoreGroupCheck: Boolean, groupName: String) {
+ wifip2pIntentReceiver.eventuallyExpectedIntent<ConnectionChanged>() {
+ val p2pGroup: WifiP2pGroup? =
+ it.intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP)
+ val groupMatched = p2pGroup?.networkName == groupName
+ return@eventuallyExpectedIntent ignoreGroupCheck || groupMatched
+ }
+ }
+
+ @Rpc(description = "Create a Wi-Fi P2P group")
+ fun createGroup(groupName: String, groupPassphrase: String) {
+ // Create a Wi-Fi P2P group
+ val wifip2pConfig = WifiP2pConfig.Builder()
+ .setNetworkName(groupName)
+ .setPassphrase(groupPassphrase)
+ .build()
+ val createGroupFuture = CompletableFuture<Boolean>()
+ wifip2pManager.createGroup(
+ wifip2pChannel,
+ wifip2pConfig,
+ object : WifiP2pManager.ActionListener {
+ override fun onFailure(reason: Int) = Unit
+ override fun onSuccess() { createGroupFuture.complete(true) }
+ }
+ )
+ createGroupFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+
+ // Ensure the Wi-Fi P2P group is created.
+ waitForP2pConnectionChanged(false, groupName)
+ }
+
+ @Rpc(description = "Start Wi-Fi P2P peers discovery")
+ fun startPeersDiscovery() {
+ // Start discovery Wi-Fi P2P peers
+ wifip2pManager.discoverPeers(wifip2pChannel, null)
+
+ // Ensure the discovery is started
+ val p2pDiscoveryStartedFuture = CompletableFuture<Boolean>()
+ wifip2pManager.requestDiscoveryState(wifip2pChannel) { state ->
+ if (state == WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED) {
+ p2pDiscoveryStartedFuture.complete(true)
+ }
+ }
+ p2pDiscoveryStartedFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ }
+
+ /**
+ * Get the device address from the given intent that matches the given device name.
+ *
+ * @param peersChangedIntent the intent to get the device address from
+ * @param deviceName the target device name
+ * @return the address of the target device or null if no devices match.
+ */
+ @Suppress("DEPRECATION")
+ private fun getDeviceAddress(peersChangedIntent: Intent, deviceName: String): String? {
+ val peers: WifiP2pDeviceList? =
+ peersChangedIntent.getParcelableExtra(WifiP2pManager.EXTRA_P2P_DEVICE_LIST)
+ return peers?.deviceList?.firstOrNull { it.deviceName == deviceName }?.deviceAddress
+ }
+
+ /**
+ * Ensure the given device has been discovered and returns the associated device address for
+ * connection.
+ *
+ * @param deviceName the target device name
+ * @return the address of the target device.
+ */
+ @Rpc(description = "Ensure the target Wi-Fi P2P device is discovered")
+ fun ensureDeviceDiscovered(deviceName: String): String {
+ val changedEvent = wifip2pIntentReceiver.eventuallyExpectedIntent<PeersChanged>() {
+ return@eventuallyExpectedIntent getDeviceAddress(it.intent, deviceName) != null
+ }
+ return getDeviceAddress(changedEvent.intent, deviceName)
+ ?: fail("Missing device in filtered intent")
+ }
+
+ @Rpc(description = "Invite a Wi-Fi P2P device to the group")
+ fun inviteDeviceToGroup(groupName: String, groupPassphrase: String, deviceAddress: String) {
+ // Connect to the device to send invitation
+ val wifip2pConfig = WifiP2pConfig.Builder()
+ .setNetworkName(groupName)
+ .setPassphrase(groupPassphrase)
+ .setDeviceAddress(MacAddress.fromString(deviceAddress))
+ .build()
+ val connectedFuture = CompletableFuture<Boolean>()
+ wifip2pManager.connect(
+ wifip2pChannel,
+ wifip2pConfig,
+ object : WifiP2pManager.ActionListener {
+ override fun onFailure(reason: Int) = Unit
+ override fun onSuccess() {
+ connectedFuture.complete(true)
+ }
+ }
+ )
+ connectedFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ }
+
+ private fun runExternalApproverForGroupProcess(
+ deviceAddress: String,
+ isGroupInvitation: Boolean
+ ) {
+ val peer = MacAddress.fromString(deviceAddress)
+ runAsShell(MANAGE_WIFI_NETWORK_SELECTION) {
+ val connectionRequestFuture = CompletableFuture<Boolean>()
+ val attachedFuture = CompletableFuture<Boolean>()
+ wifip2pManager.addExternalApprover(
+ wifip2pChannel,
+ peer,
+ object : WifiP2pManager.ExternalApproverRequestListener {
+ override fun onAttached(deviceAddress: MacAddress) {
+ attachedFuture.complete(true)
+ }
+ override fun onDetached(deviceAddress: MacAddress, reason: Int) = Unit
+ override fun onConnectionRequested(
+ requestType: Int,
+ config: WifiP2pConfig,
+ device: WifiP2pDevice
+ ) {
+ connectionRequestFuture.complete(true)
+ }
+ override fun onPinGenerated(deviceAddress: MacAddress, pin: String) = Unit
+ }
+ )
+ if (isGroupInvitation) attachedFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS) else
+ connectionRequestFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+
+ val resultFuture = CompletableFuture<Boolean>()
+ wifip2pManager.setConnectionRequestResult(
+ wifip2pChannel,
+ peer,
+ WifiP2pManager.CONNECTION_REQUEST_ACCEPT,
+ object : WifiP2pManager.ActionListener {
+ override fun onFailure(reason: Int) = Unit
+ override fun onSuccess() {
+ resultFuture.complete(true)
+ }
+ }
+ )
+ resultFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+
+ val removeFuture = CompletableFuture<Boolean>()
+ wifip2pManager.removeExternalApprover(
+ wifip2pChannel,
+ peer,
+ object : WifiP2pManager.ActionListener {
+ override fun onFailure(reason: Int) = Unit
+ override fun onSuccess() {
+ removeFuture.complete(true)
+ }
+ }
+ )
+ removeFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ }
+ }
+
+ @Rpc(description = "Accept P2P group invitation from device")
+ fun acceptGroupInvitation(deviceAddress: String) {
+ // Accept the Wi-Fi P2P group invitation
+ runExternalApproverForGroupProcess(deviceAddress, true /* isGroupInvitation */)
+ }
+
+ @Rpc(description = "Wait for connection request from the peer and accept joining")
+ fun waitForPeerConnectionRequestAndAcceptJoining(deviceAddress: String) {
+ // Wait for connection request from the peer and accept joining
+ runExternalApproverForGroupProcess(deviceAddress, false /* isGroupInvitation */)
+ }
+
+ @Rpc(description = "Ensure the target device is connected")
+ fun ensureDeviceConnected(deviceName: String) {
+ // Retrieve peers and ensure the target device is connected
+ val connectedFuture = CompletableFuture<Boolean>()
+ wifip2pManager.requestPeers(wifip2pChannel) { peers -> peers?.deviceList?.any {
+ it.deviceName == deviceName && it.status == WifiP2pDevice.CONNECTED }.let {
+ connectedFuture.complete(true)
+ }
+ }
+ connectedFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
}
}
diff --git a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
index ea3d2dd..b47b97d 100644
--- a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
@@ -54,6 +54,7 @@
import android.net.ResolverOptionsParcel;
import android.net.ResolverParamsParcel;
import android.net.RouteInfo;
+import android.net.resolv.aidl.DohParamsParcel;
import android.net.shared.PrivateDnsConfig;
import android.os.Build;
import android.provider.Settings;
@@ -327,8 +328,16 @@
@Test
public void testSendDnsConfiguration() throws Exception {
reset(mMockDnsResolver);
- mDnsManager.updatePrivateDns(new Network(TEST_NETID),
- mDnsManager.getPrivateDnsConfig());
+ final PrivateDnsConfig cfg = new PrivateDnsConfig(
+ PRIVATE_DNS_MODE_OPPORTUNISTIC /* mode */,
+ null /* hostname */,
+ null /* ips */,
+ "doh.com" /* dohName */,
+ null /* dohIps */,
+ "/some-path{?dns}" /* dohPath */,
+ 5353 /* dohPort */);
+
+ mDnsManager.updatePrivateDns(new Network(TEST_NETID), cfg);
final LinkProperties lp = new LinkProperties();
lp.setInterfaceName(TEST_IFACENAME);
lp.addDnsServer(InetAddress.getByName("3.3.3.3"));
@@ -352,7 +361,11 @@
expectedParams.transportTypes = TEST_TRANSPORT_TYPES;
expectedParams.resolverOptions = null;
expectedParams.meteredNetwork = true;
- expectedParams.dohParams = null;
+ expectedParams.dohParams = new DohParamsParcel.Builder()
+ .setName("doh.com")
+ .setDohpath("/some-path{?dns}")
+ .setPort(5353)
+ .build();
expectedParams.interfaceNames = new String[]{TEST_IFACENAME};
verify(mMockDnsResolver, times(1)).setResolverConfiguration(eq(expectedParams));
}