Merge "Make "dumpsys tethering" print the number of registered callbacks."
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 7c638a2..0ce43cc 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -19,6 +19,11 @@
"name": "TetheringIntegrationTests"
}
],
+ "postsubmit": [
+ {
+ "name": "ConnectivityCoverageTests"
+ }
+ ],
"mainline-presubmit": [
{
"name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
@@ -37,11 +42,9 @@
},
{
"name": "TetheringCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
- }
- ],
- "imports": [
+ },
{
- "path": "packages/modules/NetworkStack"
+ "name": "ConnectivityCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
}
],
"imports": [
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index d3b9393..6031646 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -37,6 +37,7 @@
"networkstack-client",
"android.hardware.tetheroffload.config-V1.0-java",
"android.hardware.tetheroffload.control-V1.0-java",
+ "android.hardware.tetheroffload.control-V1.1-java",
"net-utils-framework-common",
"net-utils-device-common",
"netd-client",
diff --git a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
index 33f1c29..a33af61 100644
--- a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
@@ -33,6 +33,8 @@
import com.android.networkstack.tethering.Tether4Value;
import com.android.networkstack.tethering.TetherStatsValue;
+import java.util.function.BiConsumer;
+
/**
* Bpf coordinator class for API shims.
*/
@@ -161,6 +163,12 @@
}
@Override
+ public void tetherOffloadRuleForEach(boolean downstream,
+ @NonNull BiConsumer<Tether4Key, Tether4Value> action) {
+ /* no op */
+ }
+
+ @Override
public boolean attachProgram(String iface, boolean downstream) {
/* no op */
return true;
diff --git a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
index 74ddcbc..611c828 100644
--- a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
@@ -47,6 +47,7 @@
import java.io.FileDescriptor;
import java.io.IOException;
+import java.util.function.BiConsumer;
/**
* Bpf coordinator class for API shims.
@@ -380,10 +381,7 @@
try {
if (downstream) {
- if (!mBpfDownstream4Map.deleteEntry(key)) {
- mLog.e("Could not delete entry (key: " + key + ")");
- return false;
- }
+ if (!mBpfDownstream4Map.deleteEntry(key)) return false; // Rule did not exist
// Decrease the rule count while a deleting rule is not using a given upstream
// interface anymore.
@@ -401,19 +399,32 @@
mRule4CountOnUpstream.put(upstreamIfindex, count);
}
} else {
- mBpfUpstream4Map.deleteEntry(key);
+ if (!mBpfUpstream4Map.deleteEntry(key)) return false; // Rule did not exist
}
} catch (ErrnoException e) {
- // Silent if the rule did not exist.
- if (e.errno != OsConstants.ENOENT) {
- mLog.e("Could not delete entry: ", e);
- return false;
- }
+ mLog.e("Could not delete entry (key: " + key + ")", e);
+ return false;
}
return true;
}
@Override
+ public void tetherOffloadRuleForEach(boolean downstream,
+ @NonNull BiConsumer<Tether4Key, Tether4Value> action) {
+ if (!isInitialized()) return;
+
+ try {
+ if (downstream) {
+ mBpfDownstream4Map.forEach(action);
+ } else {
+ mBpfUpstream4Map.forEach(action);
+ }
+ } catch (ErrnoException e) {
+ mLog.e("Could not iterate map: ", e);
+ }
+ }
+
+ @Override
public boolean attachProgram(String iface, boolean downstream) {
if (!isInitialized()) return false;
diff --git a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
index 8a7a49c..08ab9ca 100644
--- a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
+++ b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
@@ -28,6 +28,8 @@
import com.android.networkstack.tethering.Tether4Value;
import com.android.networkstack.tethering.TetherStatsValue;
+import java.util.function.BiConsumer;
+
/**
* Bpf coordinator class for API shims.
*/
@@ -145,10 +147,25 @@
/**
* Deletes a tethering IPv4 offload rule from the appropriate BPF map.
+ *
+ * @param downstream true if downstream, false if upstream.
+ * @param key the key to delete.
+ * @return true iff the map was modified, false if the key did not exist or there was an error.
*/
public abstract boolean tetherOffloadRuleRemove(boolean downstream, @NonNull Tether4Key key);
/**
+ * Iterate through the map and handle each key -> value retrieved base on the given BiConsumer.
+ *
+ * @param downstream true if downstream, false if upstream.
+ * @param action represents the action for each key -> value. The entry deletion is not
+ * allowed and use #tetherOffloadRuleRemove instead.
+ */
+ @Nullable
+ public abstract void tetherOffloadRuleForEach(boolean downstream,
+ @NonNull BiConsumer<Tether4Key, Tether4Value> action);
+
+ /**
* Whether there is currently any IPv4 rule on the specified upstream.
*/
public abstract boolean isAnyIpv4RuleOnUpstream(int ifIndex);
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 3428c1d..822bdf6 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -596,6 +596,7 @@
// into calls to InterfaceController, shared with startIPv4().
mInterfaceCtrl.clearIPv4Address();
mPrivateAddressCoordinator.releaseDownstream(this);
+ mBpfCoordinator.tetherOffloadClientClear(this);
mIpv4Address = null;
mStaticIpv4ServerAddr = null;
mStaticIpv4ClientAddr = null;
@@ -949,7 +950,6 @@
if (e.isValid()) {
mBpfCoordinator.tetherOffloadClientAdd(this, clientInfo);
} else {
- // TODO: Delete all related offload rules which are using this client.
mBpfCoordinator.tetherOffloadClientRemove(this, clientInfo);
}
}
@@ -1283,6 +1283,16 @@
super.exit();
}
+ // Note that IPv4 offload rules cleanup is implemented in BpfCoordinator while upstream
+ // state is null or changed because IPv4 and IPv6 tethering have different code flow
+ // and behaviour. While upstream is switching from offload supported interface to
+ // offload non-supportted interface, event CMD_TETHER_CONNECTION_CHANGED calls
+ // #cleanupUpstreamInterface but #cleanupUpstream because new UpstreamIfaceSet is not null.
+ // This case won't happen in IPv6 tethering because IPv6 tethering upstream state is
+ // reported by IPv6TetheringCoordinator. #cleanupUpstream is also called by unwirding
+ // adding NAT failure. In that case, the IPv4 offload rules are removed by #stopIPv4
+ // in the state machine. Once there is any case out whish is not covered by previous cases,
+ // probably consider clearing rules in #cleanupUpstream as well.
private void cleanupUpstream() {
if (mUpstreamIfaceSet == null) return;
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 4a05c9f..9b95dac 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -34,7 +34,6 @@
import android.app.usage.NetworkStatsManager;
import android.net.INetd;
-import android.net.LinkProperties;
import android.net.MacAddress;
import android.net.NetworkStats;
import android.net.NetworkStats.Entry;
@@ -42,7 +41,9 @@
import android.net.ip.ConntrackMonitor;
import android.net.ip.ConntrackMonitor.ConntrackEventConsumer;
import android.net.ip.IpServer;
+import android.net.netlink.ConntrackMessage;
import android.net.netlink.NetlinkConstants;
+import android.net.netlink.NetlinkSocket;
import android.net.netstats.provider.NetworkStatsProvider;
import android.net.util.InterfaceParams;
import android.net.util.SharedLog;
@@ -50,7 +51,9 @@
import android.os.Handler;
import android.os.SystemClock;
import android.system.ErrnoException;
+import android.system.OsConstants;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
@@ -69,6 +72,7 @@
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
@@ -120,6 +124,13 @@
}
@VisibleForTesting
+ static final int POLLING_CONNTRACK_TIMEOUT_MS = 60_000;
+ @VisibleForTesting
+ static final int NF_CONNTRACK_TCP_TIMEOUT_ESTABLISHED = 432000;
+ @VisibleForTesting
+ static final int NF_CONNTRACK_UDP_TIMEOUT_STREAM = 180;
+
+ @VisibleForTesting
enum StatsType {
STATS_PER_IFACE,
STATS_PER_UID,
@@ -228,12 +239,22 @@
// BpfCoordinatorTest needs predictable iteration order.
private final Set<Integer> mDeviceMapSet = new LinkedHashSet<>();
+ // Tracks the last IPv4 upstream index. Support single upstream only.
+ // TODO: Support multi-upstream interfaces.
+ private int mLastIPv4UpstreamIfindex = 0;
+
// Runnable that used by scheduling next polling of stats.
- private final Runnable mScheduledPollingTask = () -> {
+ private final Runnable mScheduledPollingStats = () -> {
updateForwardedStats();
maybeSchedulePollingStats();
};
+ // Runnable that used by scheduling next polling of conntrack timeout.
+ private final Runnable mScheduledPollingConntrackTimeout = () -> {
+ maybeRefreshConntrackTimeout();
+ maybeSchedulePollingConntrackTimeout();
+ };
+
// TODO: add BpfMap<TetherDownstream64Key, TetherDownstream64Value> retrieving function.
@VisibleForTesting
public abstract static class Dependencies {
@@ -263,13 +284,19 @@
}
/**
+ * Represents an estimate of elapsed time since boot in nanoseconds.
+ */
+ public long elapsedRealtimeNanos() {
+ return SystemClock.elapsedRealtimeNanos();
+ }
+
+ /**
* Check OS Build at least S.
*
* TODO: move to BpfCoordinatorShim once the test doesn't need the mocked OS build for
* testing different code flows concurrently.
*/
public boolean isAtLeastS() {
- // TODO: consider using ShimUtils.isAtLeastS.
return SdkLevel.isAtLeastS();
}
@@ -407,6 +434,7 @@
mPollingStarted = true;
maybeSchedulePollingStats();
+ maybeSchedulePollingConntrackTimeout();
mLog.i("Polling started");
}
@@ -422,9 +450,13 @@
public void stopPolling() {
if (!mPollingStarted) return;
- // Stop scheduled polling tasks and poll the latest stats from BPF maps.
- if (mHandler.hasCallbacks(mScheduledPollingTask)) {
- mHandler.removeCallbacks(mScheduledPollingTask);
+ // Stop scheduled polling conntrack timeout.
+ if (mHandler.hasCallbacks(mScheduledPollingConntrackTimeout)) {
+ mHandler.removeCallbacks(mScheduledPollingConntrackTimeout);
+ }
+ // Stop scheduled polling stats and poll the latest stats from BPF maps.
+ if (mHandler.hasCallbacks(mScheduledPollingStats)) {
+ mHandler.removeCallbacks(mScheduledPollingStats);
}
updateForwardedStats();
mPollingStarted = false;
@@ -576,6 +608,7 @@
/**
* Clear all forwarding rules for a given downstream.
* Note that this can be only called on handler thread.
+ * TODO: rename to tetherOffloadRuleClear6 because of IPv6 only.
*/
public void tetherOffloadRuleClear(@NonNull final IpServer ipServer) {
if (!isUsingBpf()) return;
@@ -647,6 +680,7 @@
/**
* Add downstream client.
+ * Note that this can be only called on handler thread.
*/
public void tetherOffloadClientAdd(@NonNull final IpServer ipServer,
@NonNull final ClientInfo client) {
@@ -661,54 +695,180 @@
}
/**
- * Remove downstream client.
+ * Remove a downstream client and its rules if any.
+ * Note that this can be only called on handler thread.
*/
public void tetherOffloadClientRemove(@NonNull final IpServer ipServer,
@NonNull final ClientInfo client) {
if (!isUsingBpf()) return;
+ // No clients on the downstream, return early.
HashMap<Inet4Address, ClientInfo> clients = mTetherClients.get(ipServer);
if (clients == null) return;
- // If no rule is removed, return early. Avoid unnecessary work on a non-existent rule
- // which may have never been added or removed already.
+ // No client is removed, return early.
if (clients.remove(client.clientAddress) == null) return;
- // Remove the downstream entry if it has no more rule.
+ // Remove the client's rules. Removing the client implies that its rules are not used
+ // anymore.
+ tetherOffloadRuleClear(client);
+
+ // Remove the downstream entry if it has no more client.
if (clients.isEmpty()) {
mTetherClients.remove(ipServer);
}
}
/**
- * Call when UpstreamNetworkState may be changed.
- * If upstream has ipv4 for tethering, update this new UpstreamNetworkState to map. The
- * upstream interface index and its address mapping is prepared for building IPv4
- * offload rule.
- *
- * TODO: Delete the unused upstream interface mapping.
- * TODO: Support ether ip upstream interface.
+ * Clear all downstream clients and their rules if any.
+ * Note that this can be only called on handler thread.
*/
- public void addUpstreamIfindexToMap(LinkProperties lp) {
- if (!mPollingStarted) return;
+ public void tetherOffloadClientClear(@NonNull final IpServer ipServer) {
+ if (!isUsingBpf()) return;
+
+ final HashMap<Inet4Address, ClientInfo> clients = mTetherClients.get(ipServer);
+ if (clients == null) return;
+
+ // Need to build a client list because the client map may be changed in the iteration.
+ for (final ClientInfo c : new ArrayList<ClientInfo>(clients.values())) {
+ tetherOffloadClientRemove(ipServer, c);
+ }
+ }
+
+ /**
+ * Clear all forwarding IPv4 rules for a given client.
+ * Note that this can be only called on handler thread.
+ */
+ private void tetherOffloadRuleClear(@NonNull final ClientInfo clientInfo) {
+ // TODO: consider removing the rules in #tetherOffloadRuleForEach once BpfMap#forEach
+ // can guarantee that deleting some pass-in rules in the BPF map iteration can still
+ // walk through every entry.
+ final Inet4Address clientAddr = clientInfo.clientAddress;
+ final Set<Integer> upstreamIndiceSet = new ArraySet<Integer>();
+ final Set<Tether4Key> deleteUpstreamRuleKeys = new ArraySet<Tether4Key>();
+ final Set<Tether4Key> deleteDownstreamRuleKeys = new ArraySet<Tether4Key>();
+
+ // Find the rules which are related with the given client.
+ mBpfCoordinatorShim.tetherOffloadRuleForEach(UPSTREAM, (k, v) -> {
+ if (Arrays.equals(k.src4, clientAddr.getAddress())) {
+ deleteUpstreamRuleKeys.add(k);
+ }
+ });
+ mBpfCoordinatorShim.tetherOffloadRuleForEach(DOWNSTREAM, (k, v) -> {
+ if (Arrays.equals(v.dst46, toIpv4MappedAddressBytes(clientAddr))) {
+ deleteDownstreamRuleKeys.add(k);
+ upstreamIndiceSet.add((int) k.iif);
+ }
+ });
+
+ // The rules should be paired on upstream and downstream map because they are added by
+ // conntrack events which have bidirectional information.
+ // TODO: Consider figuring out a way to fix. Probably delete all rules to fallback.
+ if (deleteUpstreamRuleKeys.size() != deleteDownstreamRuleKeys.size()) {
+ Log.wtf(TAG, "The deleting rule numbers are different on upstream4 and downstream4 ("
+ + "upstream: " + deleteUpstreamRuleKeys.size() + ", "
+ + "downstream: " + deleteDownstreamRuleKeys.size() + ").");
+ return;
+ }
+
+ // Delete the rules which are related with the given client.
+ for (final Tether4Key k : deleteUpstreamRuleKeys) {
+ mBpfCoordinatorShim.tetherOffloadRuleRemove(UPSTREAM, k);
+ }
+ for (final Tether4Key k : deleteDownstreamRuleKeys) {
+ mBpfCoordinatorShim.tetherOffloadRuleRemove(DOWNSTREAM, k);
+ }
+
+ // Cleanup each upstream interface by a set which avoids duplicated work on the same
+ // upstream interface. Cleaning up the same interface twice (or more) here may raise
+ // an exception because all related information were removed in the first deletion.
+ for (final int upstreamIndex : upstreamIndiceSet) {
+ maybeClearLimit(upstreamIndex);
+ }
+ }
+
+ /**
+ * Clear all forwarding IPv4 rules for a given downstream. Needed because the client may still
+ * connect on the downstream but the existing rules are not required anymore. Ex: upstream
+ * changed.
+ */
+ private void tetherOffloadRule4Clear(@NonNull final IpServer ipServer) {
+ if (!isUsingBpf()) return;
+
+ final HashMap<Inet4Address, ClientInfo> clients = mTetherClients.get(ipServer);
+ if (clients == null) return;
+
+ // The value should be unique as its key because currently the key was using from its
+ // client address of ClientInfo. See #tetherOffloadClientAdd.
+ for (final ClientInfo client : clients.values()) {
+ tetherOffloadRuleClear(client);
+ }
+ }
+
+ private boolean isValidUpstreamIpv4Address(@NonNull final InetAddress addr) {
+ if (!(addr instanceof Inet4Address)) return false;
+ Inet4Address v4 = (Inet4Address) addr;
+ if (v4.isAnyLocalAddress() || v4.isLinkLocalAddress()
+ || v4.isLoopbackAddress() || v4.isMulticastAddress()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Call when UpstreamNetworkState may be changed.
+ * If upstream has ipv4 for tethering, update this new UpstreamNetworkState
+ * to BpfCoordinator for building upstream interface index mapping. Otherwise,
+ * clear the all existing rules if any.
+ *
+ * Note that this can be only called on handler thread.
+ */
+ public void updateUpstreamNetworkState(UpstreamNetworkState ns) {
+ if (!isUsingBpf()) return;
+
+ int upstreamIndex = 0;
// This will not work on a network that is using 464xlat because hasIpv4Address will not be
// true.
// TODO: need to consider 464xlat.
- if (lp == null || !lp.hasIpv4Address()) return;
+ if (ns != null && ns.linkProperties != null && ns.linkProperties.hasIpv4Address()) {
+ // TODO: support ether ip upstream interface.
+ final InterfaceParams params = mDeps.getInterfaceParams(
+ ns.linkProperties.getInterfaceName());
+ if (params != null && !params.hasMacAddress /* raw ip upstream only */) {
+ upstreamIndex = params.index;
+ }
+ }
+ if (mLastIPv4UpstreamIfindex == upstreamIndex) return;
- // Support raw ip upstream interface only.
- final InterfaceParams params = mDeps.getInterfaceParams(lp.getInterfaceName());
- if (params == null || params.hasMacAddress) return;
+ // Clear existing rules if upstream interface is changed. The existing rules should be
+ // cleared before upstream index mapping is cleared. It can avoid that ipServer or
+ // conntrack event may use the non-existing upstream interfeace index to build a removing
+ // key while removeing the rules. Can't notify each IpServer to clear the rules as
+ // IPv6TetheringCoordinator#updateUpstreamNetworkState because the IpServer may not
+ // handle the upstream changing notification before changing upstream index mapping.
+ if (mLastIPv4UpstreamIfindex != 0) {
+ // Clear all forwarding IPv4 rules for all downstreams.
+ for (final IpServer ipserver : mTetherClients.keySet()) {
+ tetherOffloadRule4Clear(ipserver);
+ }
+ }
- Collection<InetAddress> addresses = lp.getAddresses();
- for (InetAddress addr: addresses) {
- if (addr instanceof Inet4Address) {
- Inet4Address i4addr = (Inet4Address) addr;
- if (!i4addr.isAnyLocalAddress() && !i4addr.isLinkLocalAddress()
- && !i4addr.isLoopbackAddress() && !i4addr.isMulticastAddress()) {
- mIpv4UpstreamIndices.put(i4addr, params.index);
- }
+ // Don't update mLastIPv4UpstreamIfindex before clearing existing rules if any. Need that
+ // to tell if it is required to clean the out-of-date rules.
+ mLastIPv4UpstreamIfindex = upstreamIndex;
+
+ // If link properties are valid, build the upstream information mapping. Otherwise, clear
+ // the upstream interface index mapping, to ensure that any conntrack events that arrive
+ // after the upstream is lost do not incorrectly add rules pointing at the upstream.
+ if (upstreamIndex == 0) {
+ mIpv4UpstreamIndices.clear();
+ return;
+ }
+ Collection<InetAddress> addresses = ns.linkProperties.getAddresses();
+ for (final InetAddress addr: addresses) {
+ if (isValidUpstreamIpv4Address(addr)) {
+ mIpv4UpstreamIndices.put((Inet4Address) addr, upstreamIndex);
}
}
}
@@ -793,6 +953,24 @@
dumpDevmap(pw);
pw.decreaseIndent();
+ pw.println("Client Information:");
+ pw.increaseIndent();
+ if (mTetherClients.isEmpty()) {
+ pw.println("<empty>");
+ } else {
+ pw.println(mTetherClients.toString());
+ }
+ pw.decreaseIndent();
+
+ pw.println("IPv4 Upstream Indices:");
+ pw.increaseIndent();
+ if (mIpv4UpstreamIndices.isEmpty()) {
+ pw.println("<empty>");
+ } else {
+ pw.println(mIpv4UpstreamIndices.toString());
+ }
+ pw.decreaseIndent();
+
pw.println();
pw.println("Forwarding counters:");
pw.increaseIndent();
@@ -971,14 +1149,14 @@
return;
}
if (map.isEmpty()) {
- pw.println("No interface index");
+ pw.println("<empty>");
return;
}
pw.println("ifindex (iface) -> ifindex (iface)");
pw.increaseIndent();
map.forEach((k, v) -> {
// Only get upstream interface name. Just do the best to make the index readable.
- // TODO: get downstream interface name because the index is either upstrema or
+ // TODO: get downstream interface name because the index is either upstream or
// downstream interface in dev map.
pw.println(String.format("%d (%s) -> %d (%s)", k.ifIndex, getIfName(k.ifIndex),
v.ifIndex, getIfName(v.ifIndex)));
@@ -1248,12 +1426,99 @@
return null;
}
- // Support raw ip only.
- // TODO: add ether ip support.
+ @NonNull
+ private byte[] toIpv4MappedAddressBytes(Inet4Address ia4) {
+ final byte[] addr4 = ia4.getAddress();
+ final byte[] addr6 = new byte[16];
+ addr6[10] = (byte) 0xff;
+ addr6[11] = (byte) 0xff;
+ addr6[12] = addr4[0];
+ addr6[13] = addr4[1];
+ addr6[14] = addr4[2];
+ addr6[15] = addr4[3];
+ return addr6;
+ }
+
+ @Nullable
+ private Inet4Address ipv4MappedAddressBytesToIpv4Address(final byte[] addr46) {
+ if (addr46.length != 16) return null;
+ if (addr46[0] != 0 || addr46[1] != 0 || addr46[2] != 0 || addr46[3] != 0
+ || addr46[4] != 0 || addr46[5] != 0 || addr46[6] != 0 || addr46[7] != 0
+ || addr46[8] != 0 && addr46[9] != 0 || (addr46[10] & 0xff) != 0xff
+ || (addr46[11] & 0xff) != 0xff) {
+ return null;
+ }
+
+ final byte[] addr4 = new byte[4];
+ addr4[0] = addr46[12];
+ addr4[1] = addr46[13];
+ addr4[2] = addr46[14];
+ addr4[3] = addr46[15];
+
+ return parseIPv4Address(addr4);
+ }
+
// TODO: parse CTA_PROTOINFO of conntrack event in ConntrackMonitor. For TCP, only add rules
// while TCP status is established.
@VisibleForTesting
class BpfConntrackEventConsumer implements ConntrackEventConsumer {
+ // The upstream4 and downstream4 rules are built as the following tables. Only raw ip
+ // upstream interface is supported. Note that the field "lastUsed" is only updated by
+ // BPF program which records the last used time for a given rule.
+ // TODO: support ether ip upstream interface.
+ //
+ // NAT network topology:
+ //
+ // public network (rawip) private network
+ // | UE |
+ // +------------+ V +------------+------------+ V +------------+
+ // | Sever +---------+ Upstream | Downstream +---------+ Client |
+ // +------------+ +------------+------------+ +------------+
+ //
+ // upstream4 key and value:
+ //
+ // +------+------------------------------------------------+
+ // | | TetherUpstream4Key |
+ // +------+------+------+------+------+------+------+------+
+ // |field |iif |dstMac|l4prot|src4 |dst4 |srcPor|dstPor|
+ // | | | |o | | |t |t |
+ // +------+------+------+------+------+------+------+------+
+ // |value |downst|downst|tcp/ |client|server|client|server|
+ // | |ream |ream |udp | | | | |
+ // +------+------+------+------+------+------+------+------+
+ //
+ // +------+---------------------------------------------------------------------+
+ // | | TetherUpstream4Value |
+ // +------+------+------+------+------+------+------+------+------+------+------+
+ // |field |oif |ethDst|ethSrc|ethPro|pmtu |src46 |dst46 |srcPor|dstPor|lastUs|
+ // | | |mac |mac |to | | | |t |t |ed |
+ // +------+------+------+------+------+------+------+------+------+------+------+
+ // |value |upstre|-- |-- |ETH_P_|1500 |upstre|server|upstre|server|-- |
+ // | |am | | |IP | |am | |am | | |
+ // +------+------+------+------+------+------+------+------+------+------+------+
+ //
+ // downstream4 key and value:
+ //
+ // +------+------------------------------------------------+
+ // | | TetherDownstream4Key |
+ // +------+------+------+------+------+------+------+------+
+ // |field |iif |dstMac|l4prot|src4 |dst4 |srcPor|dstPor|
+ // | | | |o | | |t |t |
+ // +------+------+------+------+------+------+------+------+
+ // |value |upstre|-- |tcp/ |server|upstre|server|upstre|
+ // | |am | |udp | |am | |am |
+ // +------+------+------+------+------+------+------+------+
+ //
+ // +------+---------------------------------------------------------------------+
+ // | | TetherDownstream4Value |
+ // +------+------+------+------+------+------+------+------+------+------+------+
+ // |field |oif |ethDst|ethSrc|ethPro|pmtu |src46 |dst46 |srcPor|dstPor|lastUs|
+ // | | |mac |mac |to | | | |t |t |ed |
+ // +------+------+------+------+------+------+------+------+------+------+------+
+ // |value |downst|client|downst|ETH_P_|1500 |server|client|server|client|-- |
+ // | |ream | |ream |IP | | | | | | |
+ // +------+------+------+------+------+------+------+------+------+------+------+
+ //
@NonNull
private Tether4Key makeTetherUpstream4Key(
@NonNull ConntrackEvent e, @NonNull ClientInfo c) {
@@ -1292,19 +1557,6 @@
0 /* lastUsed, filled by bpf prog only */);
}
- @NonNull
- private byte[] toIpv4MappedAddressBytes(Inet4Address ia4) {
- final byte[] addr4 = ia4.getAddress();
- final byte[] addr6 = new byte[16];
- addr6[10] = (byte) 0xff;
- addr6[11] = (byte) 0xff;
- addr6[12] = addr4[0];
- addr6[13] = addr4[1];
- addr6[14] = addr4[2];
- addr6[15] = addr4[3];
- return addr6;
- }
-
public void accept(ConntrackEvent e) {
final ClientInfo tetherClient = getClientInfo(e.tupleOrig.srcIp);
if (tetherClient == null) return;
@@ -1318,8 +1570,23 @@
if (e.msgType == (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8
| NetlinkConstants.IPCTNL_MSG_CT_DELETE)) {
- mBpfCoordinatorShim.tetherOffloadRuleRemove(UPSTREAM, upstream4Key);
- mBpfCoordinatorShim.tetherOffloadRuleRemove(DOWNSTREAM, downstream4Key);
+ final boolean deletedUpstream = mBpfCoordinatorShim.tetherOffloadRuleRemove(
+ UPSTREAM, upstream4Key);
+ final boolean deletedDownstream = mBpfCoordinatorShim.tetherOffloadRuleRemove(
+ DOWNSTREAM, downstream4Key);
+
+ if (!deletedUpstream && !deletedDownstream) {
+ // The rules may have been already removed by losing client or losing upstream.
+ return;
+ }
+
+ if (deletedUpstream != deletedDownstream) {
+ Log.wtf(TAG, "The bidirectional rules should be removed concurrently ("
+ + "upstream: " + deletedUpstream
+ + ", downstream: " + deletedDownstream + ")");
+ return;
+ }
+
maybeClearLimit(upstreamIndex);
return;
}
@@ -1585,14 +1852,89 @@
return Math.max(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS, configInterval);
}
+ @Nullable
+ private Inet4Address parseIPv4Address(byte[] addrBytes) {
+ try {
+ final InetAddress ia = Inet4Address.getByAddress(addrBytes);
+ if (ia instanceof Inet4Address) return (Inet4Address) ia;
+ } catch (UnknownHostException | IllegalArgumentException e) {
+ mLog.e("Failed to parse IPv4 address: " + e);
+ }
+ return null;
+ }
+
+ // Update CTA_TUPLE_ORIG timeout for a given conntrack entry. Note that there will also be
+ // coming a conntrack event to notify updated timeout.
+ private void updateConntrackTimeout(byte proto, Inet4Address src4, short srcPort,
+ Inet4Address dst4, short dstPort) {
+ if (src4 == null || dst4 == null) return;
+
+ // TODO: consider acquiring the timeout setting from nf_conntrack_* variables.
+ // - proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established
+ // - proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream
+ // See kernel document nf_conntrack-sysctl.txt.
+ final int timeoutSec = (proto == OsConstants.IPPROTO_TCP)
+ ? NF_CONNTRACK_TCP_TIMEOUT_ESTABLISHED
+ : NF_CONNTRACK_UDP_TIMEOUT_STREAM;
+ final byte[] msg = ConntrackMessage.newIPv4TimeoutUpdateRequest(
+ proto, src4, (int) srcPort, dst4, (int) dstPort, timeoutSec);
+ try {
+ NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_NETFILTER, msg);
+ } catch (ErrnoException e) {
+ mLog.e("Error updating conntrack entry ("
+ + "proto: " + proto + ", "
+ + "src4: " + src4 + ", "
+ + "srcPort: " + Short.toUnsignedInt(srcPort) + ", "
+ + "dst4: " + dst4 + ", "
+ + "dstPort: " + Short.toUnsignedInt(dstPort) + "), "
+ + "msg: " + NetlinkConstants.hexify(msg) + ", "
+ + "e: " + e);
+ }
+ }
+
+ private void maybeRefreshConntrackTimeout() {
+ final long now = mDeps.elapsedRealtimeNanos();
+
+ // Reverse the source and destination {address, port} from downstream value because
+ // #updateConntrackTimeout refresh the timeout of netlink attribute CTA_TUPLE_ORIG
+ // which is opposite direction for downstream map value.
+ mBpfCoordinatorShim.tetherOffloadRuleForEach(DOWNSTREAM, (k, v) -> {
+ if ((now - v.lastUsed) / 1_000_000 < POLLING_CONNTRACK_TIMEOUT_MS) {
+ updateConntrackTimeout((byte) k.l4proto,
+ ipv4MappedAddressBytesToIpv4Address(v.dst46), (short) v.dstPort,
+ ipv4MappedAddressBytesToIpv4Address(v.src46), (short) v.srcPort);
+ }
+ });
+
+ // TODO: Consider ignoring TCP traffic on upstream and monitor on downstream only
+ // because TCP is a bidirectional traffic. Probably don't need to extend timeout by
+ // both directions for TCP.
+ mBpfCoordinatorShim.tetherOffloadRuleForEach(UPSTREAM, (k, v) -> {
+ if ((now - v.lastUsed) / 1_000_000 < POLLING_CONNTRACK_TIMEOUT_MS) {
+ updateConntrackTimeout((byte) k.l4proto, parseIPv4Address(k.src4),
+ (short) k.srcPort, parseIPv4Address(k.dst4), (short) k.dstPort);
+ }
+ });
+ }
+
private void maybeSchedulePollingStats() {
if (!mPollingStarted) return;
- if (mHandler.hasCallbacks(mScheduledPollingTask)) {
- mHandler.removeCallbacks(mScheduledPollingTask);
+ if (mHandler.hasCallbacks(mScheduledPollingStats)) {
+ mHandler.removeCallbacks(mScheduledPollingStats);
}
- mHandler.postDelayed(mScheduledPollingTask, getPollingInterval());
+ mHandler.postDelayed(mScheduledPollingStats, getPollingInterval());
+ }
+
+ private void maybeSchedulePollingConntrackTimeout() {
+ if (!mPollingStarted) return;
+
+ if (mHandler.hasCallbacks(mScheduledPollingConntrackTimeout)) {
+ mHandler.removeCallbacks(mScheduledPollingConntrackTimeout);
+ }
+
+ mHandler.postDelayed(mScheduledPollingConntrackTimeout, POLLING_CONNTRACK_TIMEOUT_MS);
}
// Return forwarding rule map. This is used for testing only.
diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadController.java b/Tethering/src/com/android/networkstack/tethering/OffloadController.java
index 88c77b0..beb1821 100644
--- a/Tethering/src/com/android/networkstack/tethering/OffloadController.java
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadController.java
@@ -26,6 +26,9 @@
import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_0;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_1;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_NONE;
import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
import android.annotation.NonNull;
@@ -96,7 +99,8 @@
private final SharedLog mLog;
private final HashMap<String, LinkProperties> mDownstreams;
private boolean mConfigInitialized;
- private boolean mControlInitialized;
+ @OffloadHardwareInterface.OffloadHalVersion
+ private int mControlHalVersion;
private LinkProperties mUpstreamLinkProperties;
// The complete set of offload-exempt prefixes passed in via Tethering from
// all upstream and downstream sources.
@@ -112,11 +116,42 @@
private ConcurrentHashMap<String, ForwardedStats> mForwardedStats =
new ConcurrentHashMap<>(16, 0.75F, 1);
+ private static class InterfaceQuota {
+ public final long warningBytes;
+ public final long limitBytes;
+
+ public static InterfaceQuota MAX_VALUE = new InterfaceQuota(Long.MAX_VALUE, Long.MAX_VALUE);
+
+ InterfaceQuota(long warningBytes, long limitBytes) {
+ this.warningBytes = warningBytes;
+ this.limitBytes = limitBytes;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof InterfaceQuota)) return false;
+ InterfaceQuota that = (InterfaceQuota) o;
+ return warningBytes == that.warningBytes
+ && limitBytes == that.limitBytes;
+ }
+
+ @Override
+ public int hashCode() {
+ return (int) (warningBytes * 3 + limitBytes * 5);
+ }
+
+ @Override
+ public String toString() {
+ return "InterfaceQuota{" + "warning=" + warningBytes + ", limit=" + limitBytes + '}';
+ }
+ }
+
// Maps upstream interface names to interface quotas.
// Always contains the latest value received from the framework for each interface, regardless
// of whether offload is currently running (or is even supported) on that interface. Only
// includes upstream interfaces that have a quota set.
- private HashMap<String, Long> mInterfaceQuotas = new HashMap<>();
+ private HashMap<String, InterfaceQuota> mInterfaceQuotas = new HashMap<>();
// Tracking remaining alert quota. Unlike limit quota is subject to interface, the alert
// quota is interface independent and global for tether offload. Note that this is only
@@ -179,7 +214,7 @@
}
}
- mControlInitialized = mHwInterface.initOffloadControl(
+ mControlHalVersion = mHwInterface.initOffloadControl(
// OffloadHardwareInterface guarantees that these callback
// methods are called on the handler passed to it, which is the
// same as mHandler, as coordinated by the setup in Tethering.
@@ -248,6 +283,18 @@
}
@Override
+ public void onWarningReached() {
+ if (!started()) return;
+ mLog.log("onWarningReached");
+
+ updateStatsForCurrentUpstream();
+ if (mStatsProvider != null) {
+ mStatsProvider.pushTetherStats();
+ mStatsProvider.notifyWarningReached();
+ }
+ }
+
+ @Override
public void onNatTimeoutUpdate(int proto,
String srcAddr, int srcPort,
String dstAddr, int dstPort) {
@@ -261,7 +308,8 @@
mLog.i("tethering offload control not supported");
stop();
} else {
- mLog.log("tethering offload started");
+ mLog.log("tethering offload started, version: "
+ + OffloadHardwareInterface.halVerToString(mControlHalVersion));
mNatUpdateCallbacksReceived = 0;
mNatUpdateNetlinkErrors = 0;
maybeSchedulePollingStats();
@@ -278,7 +326,7 @@
updateStatsForCurrentUpstream();
mUpstreamLinkProperties = null;
mHwInterface.stopOffloadControl();
- mControlInitialized = false;
+ mControlHalVersion = OFFLOAD_HAL_VERSION_NONE;
mConfigInitialized = false;
if (mHandler.hasCallbacks(mScheduledPollingTask)) {
mHandler.removeCallbacks(mScheduledPollingTask);
@@ -287,7 +335,7 @@
}
private boolean started() {
- return mConfigInitialized && mControlInitialized;
+ return mConfigInitialized && mControlHalVersion != OFFLOAD_HAL_VERSION_NONE;
}
@VisibleForTesting
@@ -320,24 +368,35 @@
@Override
public void onSetLimit(String iface, long quotaBytes) {
+ onSetWarningAndLimit(iface, QUOTA_UNLIMITED, quotaBytes);
+ }
+
+ @Override
+ public void onSetWarningAndLimit(@NonNull String iface,
+ long warningBytes, long limitBytes) {
// Listen for all iface is necessary since upstream might be changed after limit
// is set.
mHandler.post(() -> {
- final Long curIfaceQuota = mInterfaceQuotas.get(iface);
+ final InterfaceQuota curIfaceQuota = mInterfaceQuotas.get(iface);
+ final InterfaceQuota newIfaceQuota = new InterfaceQuota(
+ warningBytes == QUOTA_UNLIMITED ? Long.MAX_VALUE : warningBytes,
+ limitBytes == QUOTA_UNLIMITED ? Long.MAX_VALUE : limitBytes);
// If the quota is set to unlimited, the value set to HAL is Long.MAX_VALUE,
// which is ~8.4 x 10^6 TiB, no one can actually reach it. Thus, it is not
// useful to set it multiple times.
// Otherwise, the quota needs to be updated to tell HAL to re-count from now even
// if the quota is the same as the existing one.
- if (null == curIfaceQuota && QUOTA_UNLIMITED == quotaBytes) return;
+ if (null == curIfaceQuota && InterfaceQuota.MAX_VALUE.equals(newIfaceQuota)) {
+ return;
+ }
- if (quotaBytes == QUOTA_UNLIMITED) {
+ if (InterfaceQuota.MAX_VALUE.equals(newIfaceQuota)) {
mInterfaceQuotas.remove(iface);
} else {
- mInterfaceQuotas.put(iface, quotaBytes);
+ mInterfaceQuotas.put(iface, newIfaceQuota);
}
- maybeUpdateDataLimit(iface);
+ maybeUpdateDataWarningAndLimit(iface);
});
}
@@ -372,7 +431,11 @@
@Override
public void onSetAlert(long quotaBytes) {
- // TODO: Ask offload HAL to notify alert without stopping traffic.
+ // Ignore set alert calls from HAL V1.1 since the hardware supports set warning now.
+ // Thus, the software polling mechanism is not needed.
+ if (!useStatsPolling()) {
+ return;
+ }
// Post it to handler thread since it access remaining quota bytes.
mHandler.post(() -> {
updateAlertQuota(quotaBytes);
@@ -457,24 +520,32 @@
private boolean isPollingStatsNeeded() {
return started() && mRemainingAlertQuota > 0
+ && useStatsPolling()
&& !TextUtils.isEmpty(currentUpstreamInterface())
&& mDeps.getTetherConfig() != null
&& mDeps.getTetherConfig().getOffloadPollInterval()
>= DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
}
- private boolean maybeUpdateDataLimit(String iface) {
- // setDataLimit may only be called while offload is occurring on this upstream.
+ private boolean useStatsPolling() {
+ return mControlHalVersion == OFFLOAD_HAL_VERSION_1_0;
+ }
+
+ private boolean maybeUpdateDataWarningAndLimit(String iface) {
+ // setDataLimit or setDataWarningAndLimit may only be called while offload is occurring
+ // on this upstream.
if (!started() || !TextUtils.equals(iface, currentUpstreamInterface())) {
return true;
}
- Long limit = mInterfaceQuotas.get(iface);
- if (limit == null) {
- limit = Long.MAX_VALUE;
+ final InterfaceQuota quota = mInterfaceQuotas.getOrDefault(iface, InterfaceQuota.MAX_VALUE);
+ final boolean ret;
+ if (mControlHalVersion >= OFFLOAD_HAL_VERSION_1_1) {
+ ret = mHwInterface.setDataWarningAndLimit(iface, quota.warningBytes, quota.limitBytes);
+ } else {
+ ret = mHwInterface.setDataLimit(iface, quota.limitBytes);
}
-
- return mHwInterface.setDataLimit(iface, limit);
+ return ret;
}
private void updateStatsForCurrentUpstream() {
@@ -628,7 +699,7 @@
maybeUpdateStats(prevUpstream);
// Data limits can only be set once offload is running on the upstream.
- success = maybeUpdateDataLimit(iface);
+ success = maybeUpdateDataWarningAndLimit(iface);
if (!success) {
// If we failed to set a data limit, don't use this upstream, because we don't want to
// blow through the data limit that we were told to apply.
@@ -696,6 +767,8 @@
}
final boolean isStarted = started();
pw.println("Offload HALs " + (isStarted ? "started" : "not started"));
+ pw.println("Offload Control HAL version: "
+ + OffloadHardwareInterface.halVerToString(mControlHalVersion));
LinkProperties lp = mUpstreamLinkProperties;
String upstream = (lp != null) ? lp.getInterfaceName() : null;
pw.println("Current upstream: " + upstream);
diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
index da5f25b..e3ac660 100644
--- a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
@@ -20,13 +20,14 @@
import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
import static android.net.util.TetheringUtils.uint16;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.hardware.tetheroffload.config.V1_0.IOffloadConfig;
import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
-import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback;
import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
+import android.hardware.tetheroffload.control.V1_1.ITetheringOffloadCallback;
import android.net.netlink.NetlinkSocket;
import android.net.netlink.StructNfGenMsg;
import android.net.netlink.StructNlMsgHdr;
@@ -38,12 +39,16 @@
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
+import android.util.Log;
+import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InterruptedIOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
@@ -82,6 +87,37 @@
private final SharedLog mLog;
private final Dependencies mDeps;
private IOffloadControl mOffloadControl;
+
+ // TODO: Use major-minor version control to prevent from defining new constants.
+ static final int OFFLOAD_HAL_VERSION_NONE = 0;
+ static final int OFFLOAD_HAL_VERSION_1_0 = 1;
+ static final int OFFLOAD_HAL_VERSION_1_1 = 2;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "OFFLOAD_HAL_VERSION_", value = {
+ OFFLOAD_HAL_VERSION_NONE,
+ OFFLOAD_HAL_VERSION_1_0,
+ OFFLOAD_HAL_VERSION_1_1
+ })
+ public @interface OffloadHalVersion {}
+ @OffloadHalVersion
+ private int mOffloadControlVersion = OFFLOAD_HAL_VERSION_NONE;
+
+ @NonNull
+ static String halVerToString(int version) {
+ switch(version) {
+ case OFFLOAD_HAL_VERSION_1_0:
+ return "1.0";
+ case OFFLOAD_HAL_VERSION_1_1:
+ return "1.1";
+ case OFFLOAD_HAL_VERSION_NONE:
+ return "None";
+ default:
+ throw new IllegalArgumentException("Unsupported version int " + version);
+ }
+
+ }
+
private TetheringOffloadCallback mTetheringOffloadCallback;
private ControlCallback mControlCallback;
@@ -105,6 +141,8 @@
public void onSupportAvailable() {}
/** Offload stopped because of usage limit reached. */
public void onStoppedLimitReached() {}
+ /** Indicate that data warning quota is reached. */
+ public void onWarningReached() {}
/** Indicate to update NAT timeout. */
public void onNatTimeoutUpdate(int proto,
@@ -167,13 +205,30 @@
}
}
- public IOffloadControl getOffloadControl() {
+ @NonNull
+ public Pair<IOffloadControl, Integer> getOffloadControl() {
+ IOffloadControl hal = null;
+ int version = OFFLOAD_HAL_VERSION_NONE;
try {
- return IOffloadControl.getService(true /*retry*/);
- } catch (RemoteException | NoSuchElementException e) {
- mLog.e("tethering offload control not supported: " + e);
- return null;
+ hal = android.hardware.tetheroffload.control
+ .V1_1.IOffloadControl.getService(true /*retry*/);
+ version = OFFLOAD_HAL_VERSION_1_1;
+ } catch (NoSuchElementException e) {
+ // Unsupported by device.
+ } catch (RemoteException e) {
+ mLog.e("Unable to get offload control " + OFFLOAD_HAL_VERSION_1_1);
}
+ if (hal == null) {
+ try {
+ hal = IOffloadControl.getService(true /*retry*/);
+ version = OFFLOAD_HAL_VERSION_1_0;
+ } catch (NoSuchElementException e) {
+ // Unsupported by device.
+ } catch (RemoteException e) {
+ mLog.e("Unable to get offload control " + OFFLOAD_HAL_VERSION_1_0);
+ }
+ }
+ return new Pair<IOffloadControl, Integer>(hal, version);
}
public NativeHandle createConntrackSocket(final int groups) {
@@ -304,23 +359,33 @@
}
}
- /** Initialize the tethering offload HAL. */
- public boolean initOffloadControl(ControlCallback controlCb) {
+ /**
+ * Initialize the tethering offload HAL.
+ *
+ * @return one of {@code OFFLOAD_HAL_VERSION_*} represents the HAL version, or
+ * {@link #OFFLOAD_HAL_VERSION_NONE} if failed.
+ */
+ public int initOffloadControl(ControlCallback controlCb) {
mControlCallback = controlCb;
if (mOffloadControl == null) {
- mOffloadControl = mDeps.getOffloadControl();
+ final Pair<IOffloadControl, Integer> halAndVersion = mDeps.getOffloadControl();
+ mOffloadControl = halAndVersion.first;
+ mOffloadControlVersion = halAndVersion.second;
if (mOffloadControl == null) {
mLog.e("tethering IOffloadControl.getService() returned null");
- return false;
+ return OFFLOAD_HAL_VERSION_NONE;
}
+ mLog.i("tethering offload control version "
+ + halVerToString(mOffloadControlVersion) + " is supported.");
}
final String logmsg = String.format("initOffloadControl(%s)",
(controlCb == null) ? "null"
: "0x" + Integer.toHexString(System.identityHashCode(controlCb)));
- mTetheringOffloadCallback = new TetheringOffloadCallback(mHandler, mControlCallback, mLog);
+ mTetheringOffloadCallback = new TetheringOffloadCallback(
+ mHandler, mControlCallback, mLog, mOffloadControlVersion);
final CbResults results = new CbResults();
try {
mOffloadControl.initOffload(
@@ -331,11 +396,11 @@
});
} catch (RemoteException e) {
record(logmsg, e);
- return false;
+ return OFFLOAD_HAL_VERSION_NONE;
}
record(logmsg, results);
- return results.mSuccess;
+ return results.mSuccess ? mOffloadControlVersion : OFFLOAD_HAL_VERSION_NONE;
}
/** Stop IOffloadControl. */
@@ -419,6 +484,33 @@
return results.mSuccess;
}
+ /** Set data warning and limit value to offload management process. */
+ public boolean setDataWarningAndLimit(String iface, long warning, long limit) {
+ if (mOffloadControlVersion < OFFLOAD_HAL_VERSION_1_1) {
+ throw new IllegalArgumentException(
+ "setDataWarningAndLimit is not supported below HAL V1.1");
+ }
+ final String logmsg =
+ String.format("setDataWarningAndLimit(%s, %d, %d)", iface, warning, limit);
+
+ final CbResults results = new CbResults();
+ try {
+ ((android.hardware.tetheroffload.control.V1_1.IOffloadControl) mOffloadControl)
+ .setDataWarningAndLimit(
+ iface, warning, limit,
+ (boolean success, String errMsg) -> {
+ results.mSuccess = success;
+ results.mErrMsg = errMsg;
+ });
+ } catch (RemoteException e) {
+ record(logmsg, e);
+ return false;
+ }
+
+ record(logmsg, results);
+ return results.mSuccess;
+ }
+
/** Set upstream parameters to offload management process. */
public boolean setUpstreamParameters(
String iface, String v4addr, String v4gateway, ArrayList<String> v6gws) {
@@ -504,35 +596,64 @@
public final Handler handler;
public final ControlCallback controlCb;
public final SharedLog log;
+ private final int mOffloadControlVersion;
- TetheringOffloadCallback(Handler h, ControlCallback cb, SharedLog sharedLog) {
+ TetheringOffloadCallback(
+ Handler h, ControlCallback cb, SharedLog sharedLog, int offloadControlVersion) {
handler = h;
controlCb = cb;
log = sharedLog;
+ this.mOffloadControlVersion = offloadControlVersion;
+ }
+
+ private void handleOnEvent(int event) {
+ switch (event) {
+ case OffloadCallbackEvent.OFFLOAD_STARTED:
+ controlCb.onStarted();
+ break;
+ case OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR:
+ controlCb.onStoppedError();
+ break;
+ case OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED:
+ controlCb.onStoppedUnsupported();
+ break;
+ case OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE:
+ controlCb.onSupportAvailable();
+ break;
+ case OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED:
+ controlCb.onStoppedLimitReached();
+ break;
+ case android.hardware.tetheroffload.control
+ .V1_1.OffloadCallbackEvent.OFFLOAD_WARNING_REACHED:
+ controlCb.onWarningReached();
+ break;
+ default:
+ log.e("Unsupported OffloadCallbackEvent: " + event);
+ }
}
@Override
public void onEvent(int event) {
+ // The implementation should never call onEvent()) if the event is already reported
+ // through newer callback.
+ if (mOffloadControlVersion > OFFLOAD_HAL_VERSION_1_0) {
+ Log.wtf(TAG, "onEvent(" + event + ") fired on HAL "
+ + halVerToString(mOffloadControlVersion));
+ }
handler.post(() -> {
- switch (event) {
- case OffloadCallbackEvent.OFFLOAD_STARTED:
- controlCb.onStarted();
- break;
- case OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR:
- controlCb.onStoppedError();
- break;
- case OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED:
- controlCb.onStoppedUnsupported();
- break;
- case OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE:
- controlCb.onSupportAvailable();
- break;
- case OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED:
- controlCb.onStoppedLimitReached();
- break;
- default:
- log.e("Unsupported OffloadCallbackEvent: " + event);
- }
+ handleOnEvent(event);
+ });
+ }
+
+ @Override
+ public void onEvent_1_1(int event) {
+ if (mOffloadControlVersion < OFFLOAD_HAL_VERSION_1_1) {
+ Log.wtf(TAG, "onEvent_1_1(" + event + ") fired on HAL "
+ + halVerToString(mOffloadControlVersion));
+ return;
+ }
+ handler.post(() -> {
+ handleOnEvent(event);
});
}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 7cb31bd..079bf9c 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -1039,7 +1039,7 @@
final boolean rndisEnabled = intent.getBooleanExtra(USB_FUNCTION_RNDIS, false);
final boolean ncmEnabled = intent.getBooleanExtra(USB_FUNCTION_NCM, false);
- mLog.log(String.format("USB bcast connected:%s configured:%s rndis:%s ncm:%s",
+ mLog.i(String.format("USB bcast connected:%s configured:%s rndis:%s ncm:%s",
usbConnected, usbConfigured, rndisEnabled, ncmEnabled));
// There are three types of ACTION_USB_STATE:
@@ -1416,7 +1416,7 @@
// If TETHERING_USB is forced to use ncm function, TETHERING_NCM would no longer be
// available.
- if (mConfig.isUsingNcm()) return TETHER_ERROR_SERVICE_UNAVAIL;
+ if (mConfig.isUsingNcm() && enable) return TETHER_ERROR_SERVICE_UNAVAIL;
UsbManager usbManager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
usbManager.setCurrentFunctions(enable ? UsbManager.FUNCTION_NCM : UsbManager.FUNCTION_NONE);
@@ -1720,13 +1720,7 @@
protected void handleNewUpstreamNetworkState(UpstreamNetworkState ns) {
mIPv6TetheringCoordinator.updateUpstreamNetworkState(ns);
mOffload.updateUpstreamNetworkState(ns);
-
- // TODO: Delete all related offload rules which are using this upstream.
- if (ns != null) {
- // Add upstream index to the map. The upstream interface index is required while
- // the conntrack event builds the offload rules.
- mBpfCoordinator.addUpstreamIfindexToMap(ns.linkProperties);
- }
+ mBpfCoordinator.updateUpstreamNetworkState(ns);
}
private void handleInterfaceServingStateActive(int mode, IpServer who) {
@@ -2561,7 +2555,7 @@
return;
}
- mLog.log("adding IpServer for: " + iface);
+ mLog.i("adding IpServer for: " + iface);
final TetherState tetherState = new TetherState(
new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mBpfCoordinator,
makeControlCallback(), mConfig.enableLegacyDhcpServer,
@@ -2576,7 +2570,7 @@
if (tetherState == null) return;
tetherState.ipServer.stop();
- mLog.log("removing IpServer for: " + iface);
+ mLog.i("removing IpServer for: " + iface);
mTetherStates.remove(iface);
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index 31fcea4..d2f44d3 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -176,7 +176,9 @@
// us an interface name. Careful consideration needs to be given to
// implications for Settings and for provisioning checks.
tetherableWifiRegexs = getResourceStringArray(res, R.array.config_tether_wifi_regexs);
- tetherableWigigRegexs = getResourceStringArray(res, R.array.config_tether_wigig_regexs);
+ // TODO: Remove entire wigig code once tethering module no longer support R devices.
+ tetherableWigigRegexs = SdkLevel.isAtLeastS()
+ ? new String[0] : getResourceStringArray(res, R.array.config_tether_wigig_regexs);
tetherableWifiP2pRegexs = getResourceStringArray(
res, R.array.config_tether_wifi_p2p_regexs);
tetherableBluetoothRegexs = getResourceStringArray(
diff --git a/Tethering/tests/mts/src/android/tethering/mts/TetheringModuleTest.java b/Tethering/tests/mts/src/android/tethering/mts/TetheringModuleTest.java
index 07aab63..ef254ff 100644
--- a/Tethering/tests/mts/src/android/tethering/mts/TetheringModuleTest.java
+++ b/Tethering/tests/mts/src/android/tethering/mts/TetheringModuleTest.java
@@ -22,7 +22,6 @@
import static android.Manifest.permission.TETHER_PRIVILEGED;
import static android.Manifest.permission.WRITE_SETTINGS;
import static android.net.TetheringManager.TETHERING_WIFI;
-import static android.net.cts.util.CtsTetheringUtils.isWifiTetheringSupported;
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
@@ -102,8 +101,7 @@
TestNetworkTracker tnt = null;
try {
- tetherEventCallback.assumeTetheringSupported();
- assumeTrue(isWifiTetheringSupported(mContext, tetherEventCallback));
+ tetherEventCallback.assumeWifiTetheringSupported(mContext);
tetherEventCallback.expectNoTetheringActive();
final TetheringInterface tetheredIface =
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index ce69cb3..378a21c 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -584,6 +584,7 @@
inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
inOrder.verify(mAddressCoordinator).releaseDownstream(any());
+ inOrder.verify(mBpfCoordinator).tetherOffloadClientClear(mIpServer);
inOrder.verify(mBpfCoordinator).stopMonitoring(mIpServer);
inOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index cc912f4..914e0d4 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -36,9 +36,13 @@
import static android.system.OsConstants.ETH_P_IPV6;
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.NETLINK_NETFILTER;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
+import static com.android.networkstack.tethering.BpfCoordinator.NF_CONNTRACK_TCP_TIMEOUT_ESTABLISHED;
+import static com.android.networkstack.tethering.BpfCoordinator.NF_CONNTRACK_UDP_TIMEOUT_STREAM;
+import static com.android.networkstack.tethering.BpfCoordinator.POLLING_CONNTRACK_TIMEOUT_MS;
import static com.android.networkstack.tethering.BpfCoordinator.StatsType;
import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_IFACE;
import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_UID;
@@ -70,13 +74,17 @@
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.MacAddress;
+import android.net.Network;
+import android.net.NetworkCapabilities;
import android.net.NetworkStats;
import android.net.TetherOffloadRuleParcel;
import android.net.TetherStatsParcel;
import android.net.ip.ConntrackMonitor;
import android.net.ip.ConntrackMonitor.ConntrackEventConsumer;
import android.net.ip.IpServer;
+import android.net.netlink.ConntrackMessage;
import android.net.netlink.NetlinkConstants;
+import android.net.netlink.NetlinkSocket;
import android.net.util.InterfaceParams;
import android.net.util.SharedLog;
import android.os.Build;
@@ -127,6 +135,8 @@
@Rule
public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+ private static final int TEST_NET_ID = 24;
+
private static final int UPSTREAM_IFINDEX = 1001;
private static final int DOWNSTREAM_IFINDEX = 1002;
@@ -217,6 +227,7 @@
// it has to access the non-static function of BPF coordinator.
private BpfConntrackEventConsumer mConsumer;
+ private long mElapsedRealtimeNanos = 0;
private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
ArgumentCaptor.forClass(ArrayList.class);
private final TestLooper mTestLooper = new TestLooper();
@@ -256,6 +267,10 @@
return mConntrackMonitor;
}
+ public long elapsedRealtimeNanos() {
+ return mElapsedRealtimeNanos;
+ }
+
@Nullable
public BpfMap<Tether4Key, Tether4Value> getBpfDownstream4Map() {
return mBpfDownstream4Map;
@@ -1340,6 +1355,11 @@
}
@NonNull
+ private Tether4Key makeDownstream4Key() {
+ return makeDownstream4Key(IPPROTO_TCP);
+ }
+
+ @NonNull
private ConntrackEvent makeTestConntrackEvent(short msgType, int proto) {
if (msgType != IPCTNL_MSG_CT_NEW && msgType != IPCTNL_MSG_CT_DELETE) {
fail("Not support message type " + msgType);
@@ -1365,7 +1385,10 @@
final LinkProperties lp = new LinkProperties();
lp.setInterfaceName(UPSTREAM_IFACE);
lp.addLinkAddress(new LinkAddress(PUBLIC_ADDR, 32 /* prefix length */));
- coordinator.addUpstreamIfindexToMap(lp);
+ final NetworkCapabilities capabilities = new NetworkCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+ coordinator.updateUpstreamNetworkState(new UpstreamNetworkState(lp, capabilities,
+ new Network(TEST_NET_ID)));
}
private void setDownstreamAndClientInformationTo(final BpfCoordinator coordinator) {
@@ -1379,8 +1402,11 @@
// was started.
coordinator.startPolling();
- // Needed because tetherOffloadRuleRemove of api31.BpfCoordinatorShimImpl only decreases
- // the count while the entry is deleted. In the other words, deleteEntry returns true.
+ // Needed because two reasons: (1) BpfConntrackEventConsumer#accept only performs cleanup
+ // when both upstream and downstream rules are removed. (2) tetherOffloadRuleRemove of
+ // api31.BpfCoordinatorShimImpl only decreases the count while the entry is deleted.
+ // In the other words, deleteEntry returns true.
+ doReturn(true).when(mBpfUpstream4Map).deleteEntry(any());
doReturn(true).when(mBpfDownstream4Map).deleteEntry(any());
// Needed because BpfCoordinator#addUpstreamIfindexToMap queries interface parameter for
@@ -1494,4 +1520,104 @@
mConsumer.accept(makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, IPPROTO_UDP));
verify(mBpfDevMap, never()).updateEntry(any(), any());
}
+
+ private void setElapsedRealtimeNanos(long nanoSec) {
+ mElapsedRealtimeNanos = nanoSec;
+ }
+
+ private void checkRefreshConntrackTimeout(final TestBpfMap<Tether4Key, Tether4Value> bpfMap,
+ final Tether4Key tcpKey, final Tether4Value tcpValue, final Tether4Key udpKey,
+ final Tether4Value udpValue) throws Exception {
+ // Both system elapsed time since boot and the rule last used time are used to measure
+ // the rule expiration. In this test, all test rules are fixed the last used time to 0.
+ // Set the different testing elapsed time to make the rule to be valid or expired.
+ //
+ // Timeline:
+ // 0 60 (seconds)
+ // +---+---+---+---+--...--+---+---+---+---+---+- ..
+ // | POLLING_CONNTRACK_TIMEOUT_MS |
+ // +---+---+---+---+--...--+---+---+---+---+---+- ..
+ // |<- valid diff ->|
+ // |<- expired diff ->|
+ // ^ ^ ^
+ // last used time elapsed time (valid) elapsed time (expired)
+ final long validTime = (POLLING_CONNTRACK_TIMEOUT_MS - 1) * 1_000_000L;
+ final long expiredTime = (POLLING_CONNTRACK_TIMEOUT_MS + 1) * 1_000_000L;
+
+ // Static mocking for NetlinkSocket.
+ MockitoSession mockSession = ExtendedMockito.mockitoSession()
+ .mockStatic(NetlinkSocket.class)
+ .startMocking();
+ try {
+ final BpfCoordinator coordinator = makeBpfCoordinator();
+ coordinator.startPolling();
+ bpfMap.insertEntry(tcpKey, tcpValue);
+ bpfMap.insertEntry(udpKey, udpValue);
+
+ // [1] Don't refresh contrack timeout.
+ setElapsedRealtimeNanos(expiredTime);
+ mTestLooper.moveTimeForward(POLLING_CONNTRACK_TIMEOUT_MS);
+ waitForIdle();
+ ExtendedMockito.verifyNoMoreInteractions(staticMockMarker(NetlinkSocket.class));
+ ExtendedMockito.clearInvocations(staticMockMarker(NetlinkSocket.class));
+
+ // [2] Refresh contrack timeout.
+ setElapsedRealtimeNanos(validTime);
+ mTestLooper.moveTimeForward(POLLING_CONNTRACK_TIMEOUT_MS);
+ waitForIdle();
+ final byte[] expectedNetlinkTcp = ConntrackMessage.newIPv4TimeoutUpdateRequest(
+ IPPROTO_TCP, PRIVATE_ADDR, (int) PRIVATE_PORT, REMOTE_ADDR,
+ (int) REMOTE_PORT, NF_CONNTRACK_TCP_TIMEOUT_ESTABLISHED);
+ final byte[] expectedNetlinkUdp = ConntrackMessage.newIPv4TimeoutUpdateRequest(
+ IPPROTO_UDP, PRIVATE_ADDR, (int) PRIVATE_PORT, REMOTE_ADDR,
+ (int) REMOTE_PORT, NF_CONNTRACK_UDP_TIMEOUT_STREAM);
+ ExtendedMockito.verify(() -> NetlinkSocket.sendOneShotKernelMessage(
+ eq(NETLINK_NETFILTER), eq(expectedNetlinkTcp)));
+ ExtendedMockito.verify(() -> NetlinkSocket.sendOneShotKernelMessage(
+ eq(NETLINK_NETFILTER), eq(expectedNetlinkUdp)));
+ ExtendedMockito.verifyNoMoreInteractions(staticMockMarker(NetlinkSocket.class));
+ ExtendedMockito.clearInvocations(staticMockMarker(NetlinkSocket.class));
+
+ // [3] Don't refresh contrack timeout if polling stopped.
+ coordinator.stopPolling();
+ mTestLooper.moveTimeForward(POLLING_CONNTRACK_TIMEOUT_MS);
+ waitForIdle();
+ ExtendedMockito.verifyNoMoreInteractions(staticMockMarker(NetlinkSocket.class));
+ ExtendedMockito.clearInvocations(staticMockMarker(NetlinkSocket.class));
+ } finally {
+ mockSession.finishMocking();
+ }
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testRefreshConntrackTimeout_Upstream4Map() throws Exception {
+ // TODO: Replace the dependencies BPF map with a non-mocked TestBpfMap object.
+ final TestBpfMap<Tether4Key, Tether4Value> bpfUpstream4Map =
+ new TestBpfMap<>(Tether4Key.class, Tether4Value.class);
+ doReturn(bpfUpstream4Map).when(mDeps).getBpfUpstream4Map();
+
+ final Tether4Key tcpKey = makeUpstream4Key(IPPROTO_TCP);
+ final Tether4Key udpKey = makeUpstream4Key(IPPROTO_UDP);
+ final Tether4Value tcpValue = makeUpstream4Value();
+ final Tether4Value udpValue = makeUpstream4Value();
+
+ checkRefreshConntrackTimeout(bpfUpstream4Map, tcpKey, tcpValue, udpKey, udpValue);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testRefreshConntrackTimeout_Downstream4Map() throws Exception {
+ // TODO: Replace the dependencies BPF map with a non-mocked TestBpfMap object.
+ final TestBpfMap<Tether4Key, Tether4Value> bpfDownstream4Map =
+ new TestBpfMap<>(Tether4Key.class, Tether4Value.class);
+ doReturn(bpfDownstream4Map).when(mDeps).getBpfDownstream4Map();
+
+ final Tether4Key tcpKey = makeDownstream4Key(IPPROTO_TCP);
+ final Tether4Key udpKey = makeDownstream4Key(IPPROTO_UDP);
+ final Tether4Value tcpValue = makeDownstream4Value();
+ final Tether4Value udpValue = makeDownstream4Value();
+
+ checkRefreshConntrackTimeout(bpfDownstream4Map, tcpKey, tcpValue, udpKey, udpValue);
+ }
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
index 9bd82f9..d800816 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
@@ -29,6 +29,8 @@
import static com.android.networkstack.tethering.OffloadController.StatsType.STATS_PER_IFACE;
import static com.android.networkstack.tethering.OffloadController.StatsType.STATS_PER_UID;
import static com.android.networkstack.tethering.OffloadHardwareInterface.ForwardedStats;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_0;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_1;
import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
import static com.android.testutils.MiscAsserts.assertContainsAll;
import static com.android.testutils.MiscAsserts.assertThrows;
@@ -56,7 +58,6 @@
import android.app.usage.NetworkStatsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
-import android.net.ITetheringStatsProvider;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -141,13 +142,14 @@
FakeSettingsProvider.clearSettingsProvider();
}
- private void setupFunctioningHardwareInterface() {
+ private void setupFunctioningHardwareInterface(int controlVersion) {
when(mHardware.initOffloadConfig()).thenReturn(true);
when(mHardware.initOffloadControl(mControlCallbackCaptor.capture()))
- .thenReturn(true);
+ .thenReturn(controlVersion);
when(mHardware.setUpstreamParameters(anyString(), any(), any(), any())).thenReturn(true);
when(mHardware.getForwardedStats(any())).thenReturn(new ForwardedStats());
when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(true);
+ when(mHardware.setDataWarningAndLimit(anyString(), anyLong(), anyLong())).thenReturn(true);
}
private void enableOffload() {
@@ -170,6 +172,7 @@
ArgumentCaptor.forClass(OffloadController.OffloadTetheringStatsProvider.class);
verify(mStatsManager).registerNetworkStatsProvider(anyString(),
tetherStatsProviderCaptor.capture());
+ reset(mStatsManager);
mTetherStatsProvider = tetherStatsProviderCaptor.getValue();
assertNotNull(mTetherStatsProvider);
mTetherStatsProviderCb = new TestableNetworkStatsProviderCbBinder();
@@ -177,10 +180,18 @@
return offload;
}
+ @Test
+ public void testStartStop() throws Exception {
+ stopOffloadController(
+ startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/));
+ stopOffloadController(
+ startOffloadController(OFFLOAD_HAL_VERSION_1_1, true /*expectStart*/));
+ }
+
@NonNull
- private OffloadController startOffloadController(boolean expectStart)
+ private OffloadController startOffloadController(int controlVersion, boolean expectStart)
throws Exception {
- setupFunctioningHardwareInterface();
+ setupFunctioningHardwareInterface(controlVersion);
final OffloadController offload = makeOffloadController();
offload.start();
@@ -208,7 +219,7 @@
when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(1);
assertThrows(SettingNotFoundException.class, () ->
Settings.Global.getInt(mContentResolver, TETHER_OFFLOAD_DISABLED));
- startOffloadController(false /*expectStart*/);
+ startOffloadController(OFFLOAD_HAL_VERSION_1_0, false /*expectStart*/);
}
@Test
@@ -216,26 +227,26 @@
when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(0);
assertThrows(SettingNotFoundException.class, () ->
Settings.Global.getInt(mContentResolver, TETHER_OFFLOAD_DISABLED));
- startOffloadController(true /*expectStart*/);
+ startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
}
@Test
public void testSettingsAllowsStart() throws Exception {
Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0);
- startOffloadController(true /*expectStart*/);
+ startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
}
@Test
public void testSettingsDisablesStart() throws Exception {
Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 1);
- startOffloadController(false /*expectStart*/);
+ startOffloadController(OFFLOAD_HAL_VERSION_1_0, false /*expectStart*/);
}
@Test
public void testSetUpstreamLinkPropertiesWorking() throws Exception {
enableOffload();
final OffloadController offload =
- startOffloadController(true /*expectStart*/);
+ startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
// In reality, the UpstreamNetworkMonitor would have passed down to us
// a covering set of local prefixes representing a minimum essential
@@ -406,7 +417,7 @@
public void testGetForwardedStats() throws Exception {
enableOffload();
final OffloadController offload =
- startOffloadController(true /*expectStart*/);
+ startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
final String ethernetIface = "eth1";
final String mobileIface = "rmnet_data0";
@@ -492,84 +503,174 @@
expectedUidStatsDiff);
}
+ /**
+ * Test OffloadController with different combinations of HAL and framework versions can set
+ * data warning and/or limit correctly.
+ */
@Test
- public void testSetInterfaceQuota() throws Exception {
+ public void testSetDataWarningAndLimit() throws Exception {
+ // Verify the OffloadController is called by R framework, where the framework doesn't send
+ // warning.
+ checkSetDataWarningAndLimit(false, OFFLOAD_HAL_VERSION_1_0);
+ checkSetDataWarningAndLimit(false, OFFLOAD_HAL_VERSION_1_1);
+ // Verify the OffloadController is called by S+ framework, where the framework sends
+ // warning along with limit.
+ checkSetDataWarningAndLimit(true, OFFLOAD_HAL_VERSION_1_0);
+ checkSetDataWarningAndLimit(true, OFFLOAD_HAL_VERSION_1_1);
+ }
+
+ private void checkSetDataWarningAndLimit(boolean isProviderSetWarning, int controlVersion)
+ throws Exception {
enableOffload();
final OffloadController offload =
- startOffloadController(true /*expectStart*/);
+ startOffloadController(controlVersion, true /*expectStart*/);
final String ethernetIface = "eth1";
final String mobileIface = "rmnet_data0";
final long ethernetLimit = 12345;
+ final long mobileWarning = 123456;
final long mobileLimit = 12345678;
final LinkProperties lp = new LinkProperties();
lp.setInterfaceName(ethernetIface);
- offload.setUpstreamLinkProperties(lp);
final InOrder inOrder = inOrder(mHardware);
- when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(true);
+ when(mHardware.setUpstreamParameters(
+ any(), any(), any(), any())).thenReturn(true);
when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(true);
+ when(mHardware.setDataWarningAndLimit(anyString(), anyLong(), anyLong())).thenReturn(true);
+ offload.setUpstreamLinkProperties(lp);
+ // Applying an interface sends the initial quota to the hardware.
+ if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) {
+ inOrder.verify(mHardware).setDataWarningAndLimit(ethernetIface, Long.MAX_VALUE,
+ Long.MAX_VALUE);
+ } else {
+ inOrder.verify(mHardware).setDataLimit(ethernetIface, Long.MAX_VALUE);
+ }
+ inOrder.verifyNoMoreInteractions();
+
+ // Verify that set to unlimited again won't cause duplicated calls to the hardware.
+ if (isProviderSetWarning) {
+ mTetherStatsProvider.onSetWarningAndLimit(ethernetIface,
+ NetworkStatsProvider.QUOTA_UNLIMITED, NetworkStatsProvider.QUOTA_UNLIMITED);
+ } else {
+ mTetherStatsProvider.onSetLimit(ethernetIface, NetworkStatsProvider.QUOTA_UNLIMITED);
+ }
+ waitForIdle();
+ inOrder.verifyNoMoreInteractions();
// Applying an interface quota to the current upstream immediately sends it to the hardware.
- mTetherStatsProvider.onSetLimit(ethernetIface, ethernetLimit);
+ if (isProviderSetWarning) {
+ mTetherStatsProvider.onSetWarningAndLimit(ethernetIface,
+ NetworkStatsProvider.QUOTA_UNLIMITED, ethernetLimit);
+ } else {
+ mTetherStatsProvider.onSetLimit(ethernetIface, ethernetLimit);
+ }
waitForIdle();
- inOrder.verify(mHardware).setDataLimit(ethernetIface, ethernetLimit);
+ if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) {
+ inOrder.verify(mHardware).setDataWarningAndLimit(ethernetIface, Long.MAX_VALUE,
+ ethernetLimit);
+ } else {
+ inOrder.verify(mHardware).setDataLimit(ethernetIface, ethernetLimit);
+ }
inOrder.verifyNoMoreInteractions();
// Applying an interface quota to another upstream does not take any immediate action.
- mTetherStatsProvider.onSetLimit(mobileIface, mobileLimit);
+ if (isProviderSetWarning) {
+ mTetherStatsProvider.onSetWarningAndLimit(mobileIface, mobileWarning, mobileLimit);
+ } else {
+ mTetherStatsProvider.onSetLimit(mobileIface, mobileLimit);
+ }
waitForIdle();
- inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong());
+ if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) {
+ inOrder.verify(mHardware, never()).setDataWarningAndLimit(anyString(), anyLong(),
+ anyLong());
+ } else {
+ inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong());
+ }
// Switching to that upstream causes the quota to be applied if the parameters were applied
// correctly.
lp.setInterfaceName(mobileIface);
offload.setUpstreamLinkProperties(lp);
waitForIdle();
- inOrder.verify(mHardware).setDataLimit(mobileIface, mobileLimit);
+ if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) {
+ inOrder.verify(mHardware).setDataWarningAndLimit(mobileIface,
+ isProviderSetWarning ? mobileWarning : Long.MAX_VALUE,
+ mobileLimit);
+ } else {
+ inOrder.verify(mHardware).setDataLimit(mobileIface, mobileLimit);
+ }
- // Setting a limit of ITetheringStatsProvider.QUOTA_UNLIMITED causes the limit to be set
+ // Setting a limit of NetworkStatsProvider.QUOTA_UNLIMITED causes the limit to be set
// to Long.MAX_VALUE.
- mTetherStatsProvider.onSetLimit(mobileIface, ITetheringStatsProvider.QUOTA_UNLIMITED);
+ if (isProviderSetWarning) {
+ mTetherStatsProvider.onSetWarningAndLimit(mobileIface,
+ NetworkStatsProvider.QUOTA_UNLIMITED, NetworkStatsProvider.QUOTA_UNLIMITED);
+ } else {
+ mTetherStatsProvider.onSetLimit(mobileIface, NetworkStatsProvider.QUOTA_UNLIMITED);
+ }
waitForIdle();
- inOrder.verify(mHardware).setDataLimit(mobileIface, Long.MAX_VALUE);
+ if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) {
+ inOrder.verify(mHardware).setDataWarningAndLimit(mobileIface, Long.MAX_VALUE,
+ Long.MAX_VALUE);
+ } else {
+ inOrder.verify(mHardware).setDataLimit(mobileIface, Long.MAX_VALUE);
+ }
- // If setting upstream parameters fails, then the data limit is not set.
+ // If setting upstream parameters fails, then the data warning and limit is not set.
when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(false);
lp.setInterfaceName(ethernetIface);
offload.setUpstreamLinkProperties(lp);
- mTetherStatsProvider.onSetLimit(mobileIface, mobileLimit);
+ if (isProviderSetWarning) {
+ mTetherStatsProvider.onSetWarningAndLimit(mobileIface, mobileWarning, mobileLimit);
+ } else {
+ mTetherStatsProvider.onSetLimit(mobileIface, mobileLimit);
+ }
waitForIdle();
inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong());
+ inOrder.verify(mHardware, never()).setDataWarningAndLimit(anyString(), anyLong(),
+ anyLong());
- // If setting the data limit fails while changing upstreams, offload is stopped.
+ // If setting the data warning and/or limit fails while changing upstreams, offload is
+ // stopped.
when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(true);
when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(false);
+ when(mHardware.setDataWarningAndLimit(anyString(), anyLong(), anyLong())).thenReturn(false);
lp.setInterfaceName(mobileIface);
offload.setUpstreamLinkProperties(lp);
- mTetherStatsProvider.onSetLimit(mobileIface, mobileLimit);
+ if (isProviderSetWarning) {
+ mTetherStatsProvider.onSetWarningAndLimit(mobileIface, mobileWarning, mobileLimit);
+ } else {
+ mTetherStatsProvider.onSetLimit(mobileIface, mobileLimit);
+ }
waitForIdle();
inOrder.verify(mHardware).getForwardedStats(ethernetIface);
inOrder.verify(mHardware).stopOffloadControl();
}
@Test
- public void testDataLimitCallback() throws Exception {
+ public void testDataWarningAndLimitCallback() throws Exception {
enableOffload();
- final OffloadController offload =
- startOffloadController(true /*expectStart*/);
+ startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
callback.onStoppedLimitReached();
mTetherStatsProviderCb.expectNotifyStatsUpdated();
+ mTetherStatsProviderCb.expectNotifyWarningOrLimitReached();
+
+ startOffloadController(OFFLOAD_HAL_VERSION_1_1, true /*expectStart*/);
+ callback = mControlCallbackCaptor.getValue();
+ callback.onWarningReached();
+ mTetherStatsProviderCb.expectNotifyStatsUpdated();
+ mTetherStatsProviderCb.expectNotifyWarningOrLimitReached();
}
@Test
public void testAddRemoveDownstreams() throws Exception {
enableOffload();
final OffloadController offload =
- startOffloadController(true /*expectStart*/);
+ startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
final InOrder inOrder = inOrder(mHardware);
// Tethering makes several calls to setLocalPrefixes() before add/remove
@@ -636,7 +737,7 @@
public void testControlCallbackOnStoppedUnsupportedFetchesAllStats() throws Exception {
enableOffload();
final OffloadController offload =
- startOffloadController(true /*expectStart*/);
+ startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
// Pretend to set a few different upstreams (only the interface name
// matters for this test; we're ignoring IP and route information).
@@ -667,7 +768,7 @@
throws Exception {
enableOffload();
final OffloadController offload =
- startOffloadController(true /*expectStart*/);
+ startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
// Pretend to set a few different upstreams (only the interface name
// matters for this test; we're ignoring IP and route information).
@@ -745,14 +846,12 @@
enableOffload();
setOffloadPollInterval(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
final OffloadController offload =
- startOffloadController(true /*expectStart*/);
+ startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
// Initialize with fake eth upstream.
final String ethernetIface = "eth1";
InOrder inOrder = inOrder(mHardware);
- final LinkProperties lp = new LinkProperties();
- lp.setInterfaceName(ethernetIface);
- offload.setUpstreamLinkProperties(lp);
+ offload.setUpstreamLinkProperties(makeEthernetLinkProperties());
// Previous upstream was null, so no stats are fetched.
inOrder.verify(mHardware, never()).getForwardedStats(any());
@@ -785,4 +884,33 @@
mTetherStatsProviderCb.assertNoCallback();
verify(mHardware, never()).getForwardedStats(any());
}
+
+ private static LinkProperties makeEthernetLinkProperties() {
+ final String ethernetIface = "eth1";
+ final LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName(ethernetIface);
+ return lp;
+ }
+
+ private void checkSoftwarePollingUsed(int controlVersion) throws Exception {
+ enableOffload();
+ setOffloadPollInterval(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
+ OffloadController offload =
+ startOffloadController(controlVersion, true /*expectStart*/);
+ offload.setUpstreamLinkProperties(makeEthernetLinkProperties());
+ mTetherStatsProvider.onSetAlert(0);
+ waitForIdle();
+ if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) {
+ mTetherStatsProviderCb.assertNoCallback();
+ } else {
+ mTetherStatsProviderCb.expectNotifyAlertReached();
+ }
+ verify(mHardware, never()).getForwardedStats(any());
+ }
+
+ @Test
+ public void testSoftwarePollingUsed() throws Exception {
+ checkSoftwarePollingUsed(OFFLOAD_HAL_VERSION_1_0);
+ checkSoftwarePollingUsed(OFFLOAD_HAL_VERSION_1_1);
+ }
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
index 38b19dd..a8b3b92 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
@@ -21,21 +21,28 @@
import static android.system.OsConstants.AF_UNIX;
import static android.system.OsConstants.SOCK_STREAM;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_0;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_1;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.hardware.tetheroffload.config.V1_0.IOffloadConfig;
import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
-import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback;
import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
-import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
+import android.hardware.tetheroffload.control.V1_1.ITetheringOffloadCallback;
+import android.hardware.tetheroffload.control.V1_1.OffloadCallbackEvent;
import android.net.netlink.StructNfGenMsg;
import android.net.netlink.StructNlMsgHdr;
import android.net.util.SharedLog;
@@ -45,6 +52,7 @@
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
+import android.util.Pair;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -53,6 +61,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -73,7 +82,7 @@
private OffloadHardwareInterface.ControlCallback mControlCallback;
@Mock private IOffloadConfig mIOffloadConfig;
- @Mock private IOffloadControl mIOffloadControl;
+ private IOffloadControl mIOffloadControl;
@Mock private NativeHandle mNativeHandle;
// Random values to test Netlink message.
@@ -81,8 +90,10 @@
private static final short TEST_FLAGS = 263;
class MyDependencies extends OffloadHardwareInterface.Dependencies {
- MyDependencies(SharedLog log) {
+ private final int mMockControlVersion;
+ MyDependencies(SharedLog log, final int mockControlVersion) {
super(log);
+ mMockControlVersion = mockControlVersion;
}
@Override
@@ -91,8 +102,20 @@
}
@Override
- public IOffloadControl getOffloadControl() {
- return mIOffloadControl;
+ public Pair<IOffloadControl, Integer> getOffloadControl() {
+ switch (mMockControlVersion) {
+ case OFFLOAD_HAL_VERSION_1_0:
+ mIOffloadControl = mock(IOffloadControl.class);
+ break;
+ case OFFLOAD_HAL_VERSION_1_1:
+ mIOffloadControl =
+ mock(android.hardware.tetheroffload.control.V1_1.IOffloadControl.class);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid offload control version "
+ + mMockControlVersion);
+ }
+ return new Pair<IOffloadControl, Integer>(mIOffloadControl, mMockControlVersion);
}
@Override
@@ -104,13 +127,13 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- final SharedLog log = new SharedLog("test");
- mOffloadHw = new OffloadHardwareInterface(new Handler(mTestLooper.getLooper()), log,
- new MyDependencies(log));
mControlCallback = spy(new OffloadHardwareInterface.ControlCallback());
}
- private void startOffloadHardwareInterface() throws Exception {
+ private void startOffloadHardwareInterface(int controlVersion) throws Exception {
+ final SharedLog log = new SharedLog("test");
+ mOffloadHw = new OffloadHardwareInterface(new Handler(mTestLooper.getLooper()), log,
+ new MyDependencies(log, controlVersion));
mOffloadHw.initOffloadConfig();
mOffloadHw.initOffloadControl(mControlCallback);
final ArgumentCaptor<ITetheringOffloadCallback> mOffloadCallbackCaptor =
@@ -121,7 +144,7 @@
@Test
public void testGetForwardedStats() throws Exception {
- startOffloadHardwareInterface();
+ startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
final OffloadHardwareInterface.ForwardedStats stats = mOffloadHw.getForwardedStats(RMNET0);
verify(mIOffloadControl).getForwardedStats(eq(RMNET0), any());
assertNotNull(stats);
@@ -129,7 +152,7 @@
@Test
public void testSetLocalPrefixes() throws Exception {
- startOffloadHardwareInterface();
+ startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
final ArrayList<String> localPrefixes = new ArrayList<>();
localPrefixes.add("127.0.0.0/8");
localPrefixes.add("fe80::/64");
@@ -139,15 +162,32 @@
@Test
public void testSetDataLimit() throws Exception {
- startOffloadHardwareInterface();
+ startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
final long limit = 12345;
mOffloadHw.setDataLimit(RMNET0, limit);
verify(mIOffloadControl).setDataLimit(eq(RMNET0), eq(limit), any());
}
@Test
+ public void testSetDataWarningAndLimit() throws Exception {
+ // Verify V1.0 control HAL would reject the function call with exception.
+ startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
+ final long warning = 12345;
+ final long limit = 67890;
+ assertThrows(IllegalArgumentException.class,
+ () -> mOffloadHw.setDataWarningAndLimit(RMNET0, warning, limit));
+ reset(mIOffloadControl);
+
+ // Verify V1.1 control HAL could receive this function call.
+ startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_1);
+ mOffloadHw.setDataWarningAndLimit(RMNET0, warning, limit);
+ verify((android.hardware.tetheroffload.control.V1_1.IOffloadControl) mIOffloadControl)
+ .setDataWarningAndLimit(eq(RMNET0), eq(warning), eq(limit), any());
+ }
+
+ @Test
public void testSetUpstreamParameters() throws Exception {
- startOffloadHardwareInterface();
+ startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
final String v4addr = "192.168.10.1";
final String v4gateway = "192.168.10.255";
final ArrayList<String> v6gws = new ArrayList<>(0);
@@ -166,7 +206,7 @@
@Test
public void testUpdateDownstreamPrefix() throws Exception {
- startOffloadHardwareInterface();
+ startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
final String ifName = "wlan1";
final String prefix = "192.168.43.0/24";
mOffloadHw.addDownstreamPrefix(ifName, prefix);
@@ -178,7 +218,7 @@
@Test
public void testTetheringOffloadCallback() throws Exception {
- startOffloadHardwareInterface();
+ startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STARTED);
mTestLooper.dispatchAll();
@@ -217,10 +257,26 @@
eq(uint16(udpParams.src.port)),
eq(udpParams.dst.addr),
eq(uint16(udpParams.dst.port)));
+ reset(mControlCallback);
+
+ startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_1);
+
+ // Verify the interface will process the events that comes from V1.1 HAL.
+ mTetheringOffloadCallback.onEvent_1_1(OffloadCallbackEvent.OFFLOAD_STARTED);
+ mTestLooper.dispatchAll();
+ final InOrder inOrder = inOrder(mControlCallback);
+ inOrder.verify(mControlCallback).onStarted();
+ inOrder.verifyNoMoreInteractions();
+
+ mTetheringOffloadCallback.onEvent_1_1(OffloadCallbackEvent.OFFLOAD_WARNING_REACHED);
+ mTestLooper.dispatchAll();
+ inOrder.verify(mControlCallback).onWarningReached();
+ inOrder.verifyNoMoreInteractions();
}
@Test
public void testSendIpv4NfGenMsg() throws Exception {
+ startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
FileDescriptor writeSocket = new FileDescriptor();
FileDescriptor readSocket = new FileDescriptor();
try {
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java
index 6090213..e692015 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java
@@ -20,6 +20,8 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static com.android.networkstack.apishim.common.ShimUtils.isAtLeastS;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
@@ -36,6 +38,7 @@
import android.os.UserHandle;
import android.util.ArrayMap;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Map;
@@ -67,10 +70,10 @@
public static final boolean BROADCAST_FIRST = false;
public static final boolean CALLBACKS_FIRST = true;
- final Map<NetworkCallback, NetworkRequestInfo> mAllCallbacks = new ArrayMap<>();
+ final Map<NetworkCallback, Handler> mAllCallbacks = new ArrayMap<>();
// This contains the callbacks tracking the system default network, whether it's registered
// with registerSystemDefaultNetworkCallback (S+) or with a custom request (R-).
- final Map<NetworkCallback, NetworkRequestInfo> mTrackingDefault = new ArrayMap<>();
+ final Map<NetworkCallback, Handler> mTrackingDefault = new ArrayMap<>();
final Map<NetworkCallback, NetworkRequestInfo> mListening = new ArrayMap<>();
final Map<NetworkCallback, NetworkRequestInfo> mRequested = new ArrayMap<>();
final Map<NetworkCallback, Integer> mLegacyTypeMap = new ArrayMap<>();
@@ -91,7 +94,7 @@
mContext = ctx;
}
- class NetworkRequestInfo {
+ static class NetworkRequestInfo {
public final NetworkRequest request;
public final Handler handler;
NetworkRequestInfo(NetworkRequest r, Handler h) {
@@ -145,15 +148,15 @@
private void sendDefaultNetworkCallbacks(TestNetworkAgent formerDefault,
TestNetworkAgent defaultNetwork) {
for (NetworkCallback cb : mTrackingDefault.keySet()) {
- final NetworkRequestInfo nri = mTrackingDefault.get(cb);
+ final Handler handler = mTrackingDefault.get(cb);
if (defaultNetwork != null) {
- nri.handler.post(() -> cb.onAvailable(defaultNetwork.networkId));
- nri.handler.post(() -> cb.onCapabilitiesChanged(
+ handler.post(() -> cb.onAvailable(defaultNetwork.networkId));
+ handler.post(() -> cb.onCapabilitiesChanged(
defaultNetwork.networkId, defaultNetwork.networkCapabilities));
- nri.handler.post(() -> cb.onLinkPropertiesChanged(
+ handler.post(() -> cb.onLinkPropertiesChanged(
defaultNetwork.networkId, defaultNetwork.linkProperties));
} else if (formerDefault != null) {
- nri.handler.post(() -> cb.onLost(formerDefault.networkId));
+ handler.post(() -> cb.onLost(formerDefault.networkId));
}
}
}
@@ -191,20 +194,33 @@
@Override
public void requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h) {
- assertFalse(mAllCallbacks.containsKey(cb));
- mAllCallbacks.put(cb, new NetworkRequestInfo(req, h));
// For R- devices, Tethering will invoke this function in 2 cases, one is to request mobile
// network, the other is to track system default network.
if (looksLikeDefaultRequest(req)) {
- assertFalse(mTrackingDefault.containsKey(cb));
- mTrackingDefault.put(cb, new NetworkRequestInfo(req, h));
+ assertFalse(isAtLeastS());
+ addTrackDefaultCallback(cb, h);
} else {
+ assertFalse(mAllCallbacks.containsKey(cb));
+ mAllCallbacks.put(cb, h);
assertFalse(mRequested.containsKey(cb));
mRequested.put(cb, new NetworkRequestInfo(req, h));
}
}
@Override
+ public void registerSystemDefaultNetworkCallback(
+ @NonNull NetworkCallback cb, @NonNull Handler h) {
+ addTrackDefaultCallback(cb, h);
+ }
+
+ private void addTrackDefaultCallback(@NonNull NetworkCallback cb, @NonNull Handler h) {
+ assertFalse(mAllCallbacks.containsKey(cb));
+ mAllCallbacks.put(cb, h);
+ assertFalse(mTrackingDefault.containsKey(cb));
+ mTrackingDefault.put(cb, h);
+ }
+
+ @Override
public void requestNetwork(NetworkRequest req, NetworkCallback cb) {
fail("Should never be called.");
}
@@ -215,7 +231,7 @@
assertFalse(mAllCallbacks.containsKey(cb));
NetworkRequest newReq = new NetworkRequest(req.networkCapabilities, legacyType,
-1 /** testId */, req.type);
- mAllCallbacks.put(cb, new NetworkRequestInfo(newReq, h));
+ mAllCallbacks.put(cb, h);
assertFalse(mRequested.containsKey(cb));
mRequested.put(cb, new NetworkRequestInfo(newReq, h));
assertFalse(mLegacyTypeMap.containsKey(cb));
@@ -227,7 +243,7 @@
@Override
public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb, Handler h) {
assertFalse(mAllCallbacks.containsKey(cb));
- mAllCallbacks.put(cb, new NetworkRequestInfo(req, h));
+ mAllCallbacks.put(cb, h);
assertFalse(mListening.containsKey(cb));
mListening.put(cb, new NetworkRequestInfo(req, h));
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 383fce1..9e0c880 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -61,8 +61,11 @@
import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_0;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_NONE;
import static com.android.networkstack.tethering.TestConnectivityManager.BROADCAST_FIRST;
import static com.android.networkstack.tethering.TestConnectivityManager.CALLBACKS_FIRST;
import static com.android.networkstack.tethering.Tethering.UserRestrictionActionListener;
@@ -595,7 +598,7 @@
mInterfaceConfiguration.flags = new String[0];
when(mRouterAdvertisementDaemon.start())
.thenReturn(true);
- initOffloadConfiguration(true /* offloadConfig */, true /* offloadControl */,
+ initOffloadConfiguration(true /* offloadConfig */, OFFLOAD_HAL_VERSION_1_0,
0 /* defaultDisabled */);
when(mOffloadHardwareInterface.getForwardedStats(any())).thenReturn(mForwardedStats);
@@ -758,10 +761,17 @@
}
private void verifyDefaultNetworkRequestFiled() {
- ArgumentCaptor<NetworkRequest> reqCaptor = ArgumentCaptor.forClass(NetworkRequest.class);
- verify(mCm, times(1)).requestNetwork(reqCaptor.capture(),
- any(NetworkCallback.class), any(Handler.class));
- assertTrue(TestConnectivityManager.looksLikeDefaultRequest(reqCaptor.getValue()));
+ if (isAtLeastS()) {
+ verify(mCm, times(1)).registerSystemDefaultNetworkCallback(
+ any(NetworkCallback.class), any(Handler.class));
+ } else {
+ ArgumentCaptor<NetworkRequest> reqCaptor = ArgumentCaptor.forClass(
+ NetworkRequest.class);
+ verify(mCm, times(1)).requestNetwork(reqCaptor.capture(),
+ any(NetworkCallback.class), any(Handler.class));
+ assertTrue(TestConnectivityManager.looksLikeDefaultRequest(reqCaptor.getValue()));
+ }
+
// The default network request is only ever filed once.
verifyNoMoreInteractions(mCm);
}
@@ -1866,7 +1876,7 @@
callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
// 1. Offload fail if no OffloadConfig.
- initOffloadConfiguration(false /* offloadConfig */, true /* offloadControl */,
+ initOffloadConfiguration(false /* offloadConfig */, OFFLOAD_HAL_VERSION_1_0,
0 /* defaultDisabled */);
runUsbTethering(upstreamState);
callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_FAILED);
@@ -1874,7 +1884,7 @@
callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
reset(mUsbManager, mIPv6TetheringCoordinator);
// 2. Offload fail if no OffloadControl.
- initOffloadConfiguration(true /* offloadConfig */, false /* offloadControl */,
+ initOffloadConfiguration(true /* offloadConfig */, OFFLOAD_HAL_VERSION_NONE,
0 /* defaultDisabled */);
runUsbTethering(upstreamState);
callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_FAILED);
@@ -1882,7 +1892,7 @@
callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
reset(mUsbManager, mIPv6TetheringCoordinator);
// 3. Offload fail if disabled by settings.
- initOffloadConfiguration(true /* offloadConfig */, true /* offloadControl */,
+ initOffloadConfiguration(true /* offloadConfig */, OFFLOAD_HAL_VERSION_1_0,
1 /* defaultDisabled */);
runUsbTethering(upstreamState);
callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_FAILED);
@@ -1900,9 +1910,10 @@
}
private void initOffloadConfiguration(final boolean offloadConfig,
- final boolean offloadControl, final int defaultDisabled) {
+ @OffloadHardwareInterface.OffloadHalVersion final int offloadControlVersion,
+ final int defaultDisabled) {
when(mOffloadHardwareInterface.initOffloadConfig()).thenReturn(offloadConfig);
- when(mOffloadHardwareInterface.initOffloadControl(any())).thenReturn(offloadControl);
+ when(mOffloadHardwareInterface.initOffloadControl(any())).thenReturn(offloadControlVersion);
when(mOffloadHardwareInterface.getDefaultTetherOffloadDisabled()).thenReturn(
defaultDisabled);
}
@@ -2599,12 +2610,57 @@
reset(mBluetoothAdapter, mBluetoothPan);
}
+ private void runDualStackUsbTethering(final String expectedIface) throws Exception {
+ when(mNetd.interfaceGetList()).thenReturn(new String[] {expectedIface});
+ when(mRouterAdvertisementDaemon.start())
+ .thenReturn(true);
+ final UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
+ runUsbTethering(upstreamState);
+
+ verify(mNetd).interfaceGetList();
+ verify(mNetd).tetherAddForward(expectedIface, TEST_MOBILE_IFNAME);
+ verify(mNetd).ipfwdAddInterfaceForward(expectedIface, TEST_MOBILE_IFNAME);
+
+ verify(mRouterAdvertisementDaemon).start();
+ verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
+ any(), any());
+ sendIPv6TetherUpdates(upstreamState);
+ assertSetIfaceToDadProxy(1 /* numOfCalls */, TEST_MOBILE_IFNAME /* ifaceName */);
+ verify(mRouterAdvertisementDaemon).buildNewRa(any(), notNull());
+ verify(mNetd).tetherApplyDnsInterfaces();
+ }
+
+ private void forceUsbTetheringUse(final int function) {
+ Settings.Global.putInt(mContentResolver, TETHER_FORCE_USB_FUNCTIONS, function);
+ final ContentObserver observer = mTethering.getSettingsObserverForTest();
+ observer.onChange(false /* selfChange */);
+ mLooper.dispatchAll();
+ }
+
+ private void verifyUsbTetheringStopDueToSettingChange(final String iface) {
+ verify(mUsbManager, times(2)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+ mTethering.interfaceRemoved(iface);
+ sendUsbBroadcast(true, true, -1 /* no functions enabled */);
+ reset(mUsbManager, mNetd, mDhcpServer, mRouterAdvertisementDaemon,
+ mIPv6TetheringCoordinator, mDadProxy);
+ }
+
@Test
- public void testUsbTetheringWithNcmFunction() throws Exception {
- when(mResources.getInteger(R.integer.config_tether_usb_functions)).thenReturn(
- TetheringConfiguration.TETHER_USB_NCM_FUNCTION);
+ public void testUsbFunctionConfigurationChange() throws Exception {
+ // Run TETHERING_NCM.
+ runNcmTethering();
+ verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
+ any(), any());
+
+ // Change the USB tethering function to NCM. Because the USB tethering function was set to
+ // RNDIS (the default), tethering is stopped.
+ forceUsbTetheringUse(TETHER_USB_NCM_FUNCTION);
+ verifyUsbTetheringStopDueToSettingChange(TEST_NCM_IFNAME);
+
+ // TODO: move this into setup after allowing configure TEST_NCM_REGEX into
+ // config_tether_usb_regexs and config_tether_ncm_regexs at the same time.
when(mResources.getStringArray(R.array.config_tether_usb_regexs))
- .thenReturn(new String[] {TEST_NCM_REGEX});
+ .thenReturn(new String[] {TEST_RNDIS_REGEX, TEST_NCM_REGEX});
sendConfigurationChanged();
// If TETHERING_USB is forced to use ncm function, TETHERING_NCM would no longer be
@@ -2614,30 +2670,16 @@
mLooper.dispatchAll();
ncmResult.assertHasResult();
- final UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
- runUsbTethering(upstreamState);
+ // Run TETHERING_USB with ncm configuration.
+ runDualStackUsbTethering(TEST_NCM_IFNAME);
- verify(mNetd).interfaceGetList();
- verify(mNetd).tetherAddForward(TEST_NCM_IFNAME, TEST_MOBILE_IFNAME);
- verify(mNetd).ipfwdAddInterfaceForward(TEST_NCM_IFNAME, TEST_MOBILE_IFNAME);
+ // Change configuration to rndis.
+ forceUsbTetheringUse(TETHER_USB_RNDIS_FUNCTION);
+ verifyUsbTetheringStopDueToSettingChange(TEST_NCM_IFNAME);
- verify(mRouterAdvertisementDaemon).start();
- verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
- any(), any());
- sendIPv6TetherUpdates(upstreamState);
- assertSetIfaceToDadProxy(1 /* numOfCalls */, TEST_MOBILE_IFNAME /* ifaceName */);
- verify(mRouterAdvertisementDaemon).buildNewRa(any(), notNull());
- verify(mNetd).tetherApplyDnsInterfaces();
-
- Settings.Global.putInt(mContentResolver, TETHER_FORCE_USB_FUNCTIONS,
- TETHER_USB_RNDIS_FUNCTION);
- final ContentObserver observer = mTethering.getSettingsObserverForTest();
- observer.onChange(false /* selfChange */);
- mLooper.dispatchAll();
- // stop TETHERING_USB and TETHERING_NCM
- verify(mUsbManager, times(2)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
- mTethering.interfaceRemoved(TEST_NCM_IFNAME);
- sendUsbBroadcast(true, true, -1 /* function */);
+ // Run TETHERING_USB with rndis configuration.
+ runDualStackUsbTethering(TEST_RNDIS_IFNAME);
+ runStopUSBTethering();
}
// TODO: Test that a request for hotspot mode doesn't interfere with an
// already operating tethering mode interface.
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
index ce4ba85..173679d 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
@@ -24,6 +24,7 @@
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
import static com.android.networkstack.tethering.UpstreamNetworkMonitor.TYPE_NONE;
import static org.junit.Assert.assertEquals;
@@ -172,12 +173,17 @@
// Verify the fired default request matches expectation.
final ArgumentCaptor<NetworkRequest> requestCaptor =
ArgumentCaptor.forClass(NetworkRequest.class);
- verify(mCM, times(1)).requestNetwork(
- requestCaptor.capture(), any(NetworkCallback.class), any(Handler.class));
- // For R- devices, Tethering will invoke this function in 2 cases, one is to
- // request mobile network, the other is to track system default network. Verify
- // the request is the one tracks default network.
- assertTrue(TestConnectivityManager.looksLikeDefaultRequest(requestCaptor.getValue()));
+
+ if (isAtLeastS()) {
+ verify(mCM).registerSystemDefaultNetworkCallback(any(), any());
+ } else {
+ verify(mCM).requestNetwork(
+ requestCaptor.capture(), any(NetworkCallback.class), any(Handler.class));
+ // For R- devices, Tethering will invoke this function in 2 cases, one is to
+ // request mobile network, the other is to track system default network. Verify
+ // the request is the one tracks default network.
+ assertTrue(TestConnectivityManager.looksLikeDefaultRequest(requestCaptor.getValue()));
+ }
mUNM.startObserveAllNetworks();
verify(mCM, times(1)).registerNetworkCallback(
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index b8f8aae..7e2f688 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -4713,6 +4713,22 @@
}
/**
+ * Temporarily allow bad wifi to override {@code config_networkAvoidBadWifi} configuration.
+ *
+ * @param timeMs The expired current time. The value should be set within a limited time from
+ * now.
+ *
+ * @hide
+ */
+ public void setTestAllowBadWifiUntil(long timeMs) {
+ try {
+ mService.setTestAllowBadWifiUntil(timeMs);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Requests that the system open the captive portal app on the specified network.
*
* <p>This is to be used on networks where a captive portal was detected, as per
diff --git a/framework/src/android/net/ConnectivitySettingsManager.java b/framework/src/android/net/ConnectivitySettingsManager.java
index 4644e4f..085de6b 100644
--- a/framework/src/android/net/ConnectivitySettingsManager.java
+++ b/framework/src/android/net/ConnectivitySettingsManager.java
@@ -562,7 +562,7 @@
public static void setNetworkSwitchNotificationMaximumDailyCount(@NonNull Context context,
@IntRange(from = 0) int count) {
if (count < 0) {
- throw new IllegalArgumentException("Count must be 0~10.");
+ throw new IllegalArgumentException("Count must be more than 0.");
}
Settings.Global.putInt(
context.getContentResolver(), NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT, count);
@@ -585,6 +585,7 @@
/**
* Set minimum duration (to {@link Settings}) between each switching network notifications.
+ * The duration will be rounded down to the next millisecond, and must be positive.
*
* @param context The {@link Context} to set the setting.
* @param duration The minimum duration between notifications when switching networks.
@@ -612,10 +613,11 @@
/**
* Set URL (to {@link Settings}) used for HTTP captive portal detection upon a new connection.
- * This URL should respond with a 204 response to a GET request to indicate no captive portal is
- * present. And this URL must be HTTP as redirect responses are used to find captive portal
- * sign-in pages. If the URL set to null or be incorrect, it will result in captive portal
- * detection failed and lost the connection.
+ * The URL is accessed to check for connectivity and presence of a captive portal on a network.
+ * The URL should respond with HTTP status 204 to a GET request, and the stack will use
+ * redirection status as a signal for captive portal detection.
+ * If the URL is set to null or is otherwise incorrect or inaccessible, the stack will fail to
+ * detect connectivity and portals. This will often result in loss of connectivity.
*
* @param context The {@link Context} to set the setting.
* @param url The URL used for HTTP captive portal detection upon a new connection.
@@ -819,6 +821,7 @@
/**
* Set duration (to {@link Settings}) to keep a PendingIntent-based request.
+ * The duration will be rounded down to the next millisecond, and must be positive.
*
* @param context The {@link Context} to set the setting.
* @param duration The duration to keep a PendingIntent-based request.
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index c434bbc..50ec781 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -226,4 +226,6 @@
void offerNetwork(int providerId, in NetworkScore score,
in NetworkCapabilities caps, in INetworkOfferCallback callback);
void unofferNetwork(in INetworkOfferCallback callback);
+
+ void setTestAllowBadWifiUntil(long timeMs);
}
diff --git a/framework/src/android/net/util/MultinetworkPolicyTracker.java b/framework/src/android/net/util/MultinetworkPolicyTracker.java
index 0b42a00..7e62d28 100644
--- a/framework/src/android/net/util/MultinetworkPolicyTracker.java
+++ b/framework/src/android/net/util/MultinetworkPolicyTracker.java
@@ -75,6 +75,7 @@
private volatile boolean mAvoidBadWifi = true;
private volatile int mMeteredMultipathPreference;
private int mActiveSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private volatile long mTestAllowBadWifiUntilMs = 0;
// Mainline module can't use internal HandlerExecutor, so add an identical executor here.
private static class HandlerExecutor implements Executor {
@@ -162,14 +163,31 @@
* Whether the device or carrier configuration disables avoiding bad wifi by default.
*/
public boolean configRestrictsAvoidBadWifi() {
+ final boolean allowBadWifi = mTestAllowBadWifiUntilMs > 0
+ && mTestAllowBadWifiUntilMs > System.currentTimeMillis();
+ // If the config returns true, then avoid bad wifi design can be controlled by the
+ // NETWORK_AVOID_BAD_WIFI setting.
+ if (allowBadWifi) return true;
+
// TODO: use R.integer.config_networkAvoidBadWifi directly
final int id = mResources.get().getIdentifier("config_networkAvoidBadWifi",
"integer", mResources.getResourcesContext().getPackageName());
return (getResourcesForActiveSubId().getInteger(id) == 0);
}
+ /**
+ * Temporarily allow bad wifi to override {@code config_networkAvoidBadWifi} configuration.
+ * The value works when the time set is more than {@link System.currentTimeMillis()}.
+ */
+ public void setTestAllowBadWifiUntil(long timeMs) {
+ Log.d(TAG, "setTestAllowBadWifiUntil: " + mTestAllowBadWifiUntilMs);
+ mTestAllowBadWifiUntilMs = timeMs;
+ updateAvoidBadWifi();
+ }
+
+ @VisibleForTesting
@NonNull
- private Resources getResourcesForActiveSubId() {
+ protected Resources getResourcesForActiveSubId() {
return SubscriptionManager.getResourcesForSubId(
mResources.getResourcesContext(), mActiveSubId);
}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index fd8397f..352d266 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -651,6 +651,12 @@
private static final int EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED = 54;
/**
+ * Event to set temporary allow bad wifi within a limited time to override
+ * {@code config_networkAvoidBadWifi}.
+ */
+ private static final int EVENT_SET_TEST_ALLOW_BAD_WIFI_UNTIL = 55;
+
+ /**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be shown.
*/
@@ -662,6 +668,11 @@
*/
private static final int PROVISIONING_NOTIFICATION_HIDE = 0;
+ /**
+ * The maximum alive time to allow bad wifi configuration for testing.
+ */
+ private static final long MAX_TEST_ALLOW_BAD_WIFI_UNTIL_MS = 5 * 60 * 1000L;
+
private static String eventName(int what) {
return sMagicDecoderRing.get(what, Integer.toString(what));
}
@@ -4334,6 +4345,22 @@
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_AVOID_UNVALIDATED, network));
}
+ @Override
+ public void setTestAllowBadWifiUntil(long timeMs) {
+ enforceSettingsPermission();
+ if (!Build.isDebuggable()) {
+ throw new IllegalStateException("Does not support in non-debuggable build");
+ }
+
+ if (timeMs > System.currentTimeMillis() + MAX_TEST_ALLOW_BAD_WIFI_UNTIL_MS) {
+ throw new IllegalArgumentException("It should not exceed "
+ + MAX_TEST_ALLOW_BAD_WIFI_UNTIL_MS + "ms from now");
+ }
+
+ mHandler.sendMessage(
+ mHandler.obtainMessage(EVENT_SET_TEST_ALLOW_BAD_WIFI_UNTIL, timeMs));
+ }
+
private void handleSetAcceptUnvalidated(Network network, boolean accept, boolean always) {
if (DBG) log("handleSetAcceptUnvalidated network=" + network +
" accept=" + accept + " always=" + always);
@@ -4876,6 +4903,10 @@
case EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED:
handleMobileDataPreferredUidsChanged();
break;
+ case EVENT_SET_TEST_ALLOW_BAD_WIFI_UNTIL:
+ final long timeMs = ((Long) msg.obj).longValue();
+ mMultinetworkPolicyTracker.setTestAllowBadWifiUntil(timeMs);
+ break;
}
}
}
@@ -7566,9 +7597,16 @@
// If apps could file multi-layer requests with PendingIntents, they'd need to know
// which of the layer is satisfied alongside with some ID for the request. Hence, if
// such an API is ever implemented, there is no doubt the right request to send in
- // EXTRA_NETWORK_REQUEST is mActiveRequest, and whatever ID would be added would need to
- // be sent as a separate extra.
- intent.putExtra(ConnectivityManager.EXTRA_NETWORK_REQUEST, nri.getActiveRequest());
+ // EXTRA_NETWORK_REQUEST is the active request, and whatever ID would be added would
+ // need to be sent as a separate extra.
+ final NetworkRequest req = nri.isMultilayerRequest()
+ ? nri.getActiveRequest()
+ // Non-multilayer listen requests do not have an active request
+ : nri.mRequests.get(0);
+ if (req == null) {
+ Log.wtf(TAG, "No request in NRI " + nri);
+ }
+ intent.putExtra(ConnectivityManager.EXTRA_NETWORK_REQUEST, req);
nri.mPendingIntentSent = true;
sendIntent(nri.mPendingIntent, intent);
}
diff --git a/service/src/com/android/server/connectivity/NetworkRanker.java b/service/src/com/android/server/connectivity/NetworkRanker.java
index e839837..d7eb9c8 100644
--- a/service/src/com/android/server/connectivity/NetworkRanker.java
+++ b/service/src/com/android/server/connectivity/NetworkRanker.java
@@ -108,7 +108,58 @@
}
}
- @Nullable private <T extends Scoreable> T getBestNetworkByPolicy(
+ private <T extends Scoreable> boolean isBadWiFi(@NonNull final T candidate) {
+ return candidate.getScore().hasPolicy(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD)
+ && candidate.getCapsNoCopy().hasTransport(TRANSPORT_WIFI);
+ }
+
+ /**
+ * Apply the "yield to bad WiFi" policy.
+ *
+ * This function must run immediately after the validation policy.
+ *
+ * If any of the accepted networks has the "yield to bad WiFi" policy AND there are some
+ * bad WiFis in the rejected list, then move the networks with the policy to the rejected
+ * list. If this leaves no accepted network, then move the bad WiFis back to the accepted list.
+ *
+ * This function returns nothing, but will have updated accepted and rejected in-place.
+ *
+ * @param accepted networks accepted by the validation policy
+ * @param rejected networks rejected by the validation policy
+ */
+ private <T extends Scoreable> void applyYieldToBadWifiPolicy(@NonNull ArrayList<T> accepted,
+ @NonNull ArrayList<T> rejected) {
+ if (!CollectionUtils.any(accepted, n -> n.getScore().hasPolicy(POLICY_YIELD_TO_BAD_WIFI))) {
+ // No network with the policy : do nothing.
+ return;
+ }
+ if (!CollectionUtils.any(rejected, n -> isBadWiFi(n))) {
+ // No bad WiFi : do nothing.
+ return;
+ }
+ if (CollectionUtils.all(accepted, n -> n.getScore().hasPolicy(POLICY_YIELD_TO_BAD_WIFI))) {
+ // All validated networks yield to bad WiFis : keep bad WiFis alongside with the
+ // yielders. This is important because the yielders need to be compared to the bad
+ // wifis by the following policies (e.g. exiting).
+ final ArrayList<T> acceptedYielders = new ArrayList<>(accepted);
+ final ArrayList<T> rejectedWithBadWiFis = new ArrayList<>(rejected);
+ partitionInto(rejectedWithBadWiFis, n -> isBadWiFi(n), accepted, rejected);
+ accepted.addAll(acceptedYielders);
+ return;
+ }
+ // Only some of the validated networks yield to bad WiFi : keep only the ones who don't.
+ final ArrayList<T> acceptedWithYielders = new ArrayList<>(accepted);
+ partitionInto(acceptedWithYielders, n -> !n.getScore().hasPolicy(POLICY_YIELD_TO_BAD_WIFI),
+ accepted, rejected);
+ }
+
+ /**
+ * Get the best network among a list of candidates according to policy.
+ * @param candidates the candidates
+ * @param currentSatisfier the current satisfier, or null if none
+ * @return the best network
+ */
+ @Nullable public <T extends Scoreable> T getBestNetworkByPolicy(
@NonNull List<T> candidates,
@Nullable final T currentSatisfier) {
// Used as working areas.
@@ -148,24 +199,15 @@
if (accepted.size() == 1) return accepted.get(0);
if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
- // Yield to bad wifi policy : if any wifi has ever been validated (even if it's now
- // unvalidated), and unless it's been explicitly avoided when bad in UI, then keep only
- // networks that don't yield to such a wifi network.
- final boolean anyWiFiEverValidated = CollectionUtils.any(candidates,
- nai -> nai.getScore().hasPolicy(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD)
- && nai.getCapsNoCopy().hasTransport(TRANSPORT_WIFI));
- if (anyWiFiEverValidated) {
- partitionInto(candidates, nai -> !nai.getScore().hasPolicy(POLICY_YIELD_TO_BAD_WIFI),
- accepted, rejected);
- if (accepted.size() == 1) return accepted.get(0);
- if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
- }
-
// If any network is validated (or should be accepted even if it's not validated), then
// don't choose one that isn't.
partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_IS_VALIDATED)
|| nai.getScore().hasPolicy(POLICY_ACCEPT_UNVALIDATED),
accepted, rejected);
+ // Yield to bad wifi policy : if any network has the "yield to bad WiFi" policy and
+ // there are bad WiFis connected, then accept the bad WiFis and reject the networks with
+ // the policy.
+ applyYieldToBadWifiPolicy(accepted, rejected);
if (accepted.size() == 1) return accepted.get(0);
if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
@@ -194,16 +236,26 @@
// subscription with the same transport.
partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_TRANSPORT_PRIMARY),
accepted, rejected);
- for (final Scoreable defaultSubNai : accepted) {
- // Remove all networks without the DEFAULT_SUBSCRIPTION policy and the same transports
- // as a network that has it.
- final int[] transports = defaultSubNai.getCapsNoCopy().getTransportTypes();
- candidates.removeIf(nai -> !nai.getScore().hasPolicy(POLICY_TRANSPORT_PRIMARY)
- && Arrays.equals(transports, nai.getCapsNoCopy().getTransportTypes()));
+ if (accepted.size() > 0) {
+ // Some networks are primary for their transport. For each transport, keep only the
+ // primary, but also keep all networks for which there isn't a primary (which are now
+ // in the |rejected| array).
+ // So for each primary network, remove from |rejected| all networks with the same
+ // transports as one of the primary networks. The remaining networks should be accepted.
+ for (final T defaultSubNai : accepted) {
+ final int[] transports = defaultSubNai.getCapsNoCopy().getTransportTypes();
+ rejected.removeIf(
+ nai -> Arrays.equals(transports, nai.getCapsNoCopy().getTransportTypes()));
+ }
+ // Now the |rejected| list contains networks with transports for which there isn't
+ // a primary network. Add them back to the candidates.
+ accepted.addAll(rejected);
+ candidates = new ArrayList<>(accepted);
}
if (1 == candidates.size()) return candidates.get(0);
- // It's guaranteed candidates.size() > 0 because there is at least one with the
- // TRANSPORT_PRIMARY policy and only those without it were removed.
+ // If there were no primary network, then candidates.size() > 0 because it didn't
+ // change from the previous result. If there were, it's guaranteed candidates.size() > 0
+ // because accepted.size() > 0 above.
// If some of the networks have a better transport than others, keep only the ones with
// the best transports.
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
old mode 100644
new mode 100755
index 32e06e5..99118ac
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -230,11 +230,11 @@
boolean hasRestrictedPermission = hasRestrictedNetworkPermission(app);
if (isNetwork || hasRestrictedPermission) {
- Boolean permission = mApps.get(uid);
+ Boolean permission = mApps.get(UserHandle.getAppId(uid));
// If multiple packages share a UID (cf: android:sharedUserId) and ask for different
// permissions, don't downgrade (i.e., if it's already SYSTEM, leave it as is).
if (permission == null || permission == NETWORK) {
- mApps.put(uid, hasRestrictedPermission);
+ mApps.put(UserHandle.getAppId(uid), hasRestrictedPermission);
}
}
@@ -325,14 +325,14 @@
// networks. mApps contains the result of checks for both hasNetworkPermission and
// hasRestrictedNetworkPermission. If uid is in the mApps list that means uid has one of
// permissions at least.
- return mApps.containsKey(uid);
+ return mApps.containsKey(UserHandle.getAppId(uid));
}
/**
* Returns whether the given uid has permission to use restricted networks.
*/
public synchronized boolean hasRestrictedNetworksPermission(int uid) {
- return Boolean.TRUE.equals(mApps.get(uid));
+ return Boolean.TRUE.equals(mApps.get(UserHandle.getAppId(uid)));
}
private void update(Set<UserHandle> users, Map<Integer, Boolean> apps, boolean add) {
@@ -452,12 +452,13 @@
// If multiple packages share a UID (cf: android:sharedUserId) and ask for different
// permissions, don't downgrade (i.e., if it's already SYSTEM, leave it as is).
- final Boolean permission = highestPermissionForUid(mApps.get(uid), packageName);
- if (permission != mApps.get(uid)) {
- mApps.put(uid, permission);
+ final int appId = UserHandle.getAppId(uid);
+ final Boolean permission = highestPermissionForUid(mApps.get(appId), packageName);
+ if (permission != mApps.get(appId)) {
+ mApps.put(appId, permission);
Map<Integer, Boolean> apps = new HashMap<>();
- apps.put(uid, permission);
+ apps.put(appId, permission);
update(mUsers, apps, true);
}
@@ -472,7 +473,7 @@
updateVpnUids(vpn.getKey(), changedUids, true);
}
}
- mAllApps.add(UserHandle.getAppId(uid));
+ mAllApps.add(appId);
}
private Boolean highestUidNetworkPermission(int uid) {
@@ -529,16 +530,17 @@
return;
}
- if (permission == mApps.get(uid)) {
+ final int appId = UserHandle.getAppId(uid);
+ if (permission == mApps.get(appId)) {
// The permissions of this UID have not changed. Nothing to do.
return;
} else if (permission != null) {
- mApps.put(uid, permission);
- apps.put(uid, permission);
+ mApps.put(appId, permission);
+ apps.put(appId, permission);
update(mUsers, apps, true);
} else {
- mApps.remove(uid);
- apps.put(uid, NETWORK); // doesn't matter which permission we pick here
+ mApps.remove(appId);
+ apps.put(appId, NETWORK); // doesn't matter which permission we pick here
update(mUsers, apps, false);
}
}
@@ -653,7 +655,7 @@
*/
private void removeBypassingUids(Set<Integer> uids, int vpnAppUid) {
uids.remove(vpnAppUid);
- uids.removeIf(uid -> mApps.getOrDefault(uid, NETWORK) == SYSTEM);
+ uids.removeIf(uid -> mApps.getOrDefault(UserHandle.getAppId(uid), NETWORK) == SYSTEM);
}
/**
@@ -795,12 +797,13 @@
for (Integer uid : uidsToUpdate) {
final Boolean permission = highestUidNetworkPermission(uid);
+ final int appId = UserHandle.getAppId(uid);
if (null == permission) {
- removedUids.put(uid, NETWORK); // Doesn't matter which permission is set here.
- mApps.remove(uid);
+ removedUids.put(appId, NETWORK); // Doesn't matter which permission is set here.
+ mApps.remove(appId);
} else {
- updatedUids.put(uid, permission);
- mApps.put(uid, permission);
+ updatedUids.put(appId, permission);
+ mApps.put(appId, permission);
}
}
diff --git a/tests/common/AndroidTest_Coverage.xml b/tests/common/AndroidTest_Coverage.xml
index 577f36a..7c8e710 100644
--- a/tests/common/AndroidTest_Coverage.xml
+++ b/tests/common/AndroidTest_Coverage.xml
@@ -18,6 +18,7 @@
</target_preparer>
<option name="test-tag" value="ConnectivityCoverageTests" />
+ <option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.connectivity.tests.coverage" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
diff --git a/tests/unit/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/common/java/android/net/ConnectivityDiagnosticsManagerTest.java
similarity index 98%
rename from tests/unit/java/android/net/ConnectivityDiagnosticsManagerTest.java
rename to tests/common/java/android/net/ConnectivityDiagnosticsManagerTest.java
index 06e9405..294ed10 100644
--- a/tests/unit/java/android/net/ConnectivityDiagnosticsManagerTest.java
+++ b/tests/common/java/android/net/ConnectivityDiagnosticsManagerTest.java
@@ -36,9 +36,11 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.content.Context;
+import android.os.Build;
import android.os.PersistableBundle;
import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
import org.junit.After;
import org.junit.Before;
@@ -50,6 +52,7 @@
import java.util.concurrent.Executor;
@RunWith(JUnit4.class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class ConnectivityDiagnosticsManagerTest {
private static final int NET_ID = 1;
private static final int DETECTION_METHOD = 2;
diff --git a/tests/common/java/android/net/ConnectivitySettingsManagerTest.kt b/tests/common/java/android/net/ConnectivitySettingsManagerTest.kt
new file mode 100644
index 0000000..ebaa787
--- /dev/null
+++ b/tests/common/java/android/net/ConnectivitySettingsManagerTest.kt
@@ -0,0 +1,295 @@
+/*
+ * 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
+
+import android.net.ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE
+import android.net.ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_AVOID
+import android.net.ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_IGNORE
+import android.net.ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_PROMPT
+import android.net.ConnectivitySettingsManager.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS
+import android.net.ConnectivitySettingsManager.DATA_ACTIVITY_TIMEOUT_MOBILE
+import android.net.ConnectivitySettingsManager.DATA_ACTIVITY_TIMEOUT_WIFI
+import android.net.ConnectivitySettingsManager.DNS_RESOLVER_MAX_SAMPLES
+import android.net.ConnectivitySettingsManager.DNS_RESOLVER_MIN_SAMPLES
+import android.net.ConnectivitySettingsManager.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS
+import android.net.ConnectivitySettingsManager.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT
+import android.net.ConnectivitySettingsManager.MOBILE_DATA_ALWAYS_ON
+import android.net.ConnectivitySettingsManager.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT
+import android.net.ConnectivitySettingsManager.NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS
+import android.net.ConnectivitySettingsManager.PRIVATE_DNS_DEFAULT_MODE
+import android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF
+import android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC
+import android.net.ConnectivitySettingsManager.WIFI_ALWAYS_REQUESTED
+import android.net.ConnectivitySettingsManager.getCaptivePortalMode
+import android.net.ConnectivitySettingsManager.getConnectivityKeepPendingIntentDuration
+import android.net.ConnectivitySettingsManager.getDnsResolverSampleRanges
+import android.net.ConnectivitySettingsManager.getDnsResolverSampleValidityDuration
+import android.net.ConnectivitySettingsManager.getDnsResolverSuccessThresholdPercent
+import android.net.ConnectivitySettingsManager.getMobileDataActivityTimeout
+import android.net.ConnectivitySettingsManager.getMobileDataAlwaysOn
+import android.net.ConnectivitySettingsManager.getNetworkSwitchNotificationMaximumDailyCount
+import android.net.ConnectivitySettingsManager.getNetworkSwitchNotificationRateDuration
+import android.net.ConnectivitySettingsManager.getPrivateDnsDefaultMode
+import android.net.ConnectivitySettingsManager.getWifiAlwaysRequested
+import android.net.ConnectivitySettingsManager.getWifiDataActivityTimeout
+import android.net.ConnectivitySettingsManager.setCaptivePortalMode
+import android.net.ConnectivitySettingsManager.setConnectivityKeepPendingIntentDuration
+import android.net.ConnectivitySettingsManager.setDnsResolverSampleRanges
+import android.net.ConnectivitySettingsManager.setDnsResolverSampleValidityDuration
+import android.net.ConnectivitySettingsManager.setDnsResolverSuccessThresholdPercent
+import android.net.ConnectivitySettingsManager.setMobileDataActivityTimeout
+import android.net.ConnectivitySettingsManager.setMobileDataAlwaysOn
+import android.net.ConnectivitySettingsManager.setNetworkSwitchNotificationMaximumDailyCount
+import android.net.ConnectivitySettingsManager.setNetworkSwitchNotificationRateDuration
+import android.net.ConnectivitySettingsManager.setPrivateDnsDefaultMode
+import android.net.ConnectivitySettingsManager.setWifiAlwaysRequested
+import android.net.ConnectivitySettingsManager.setWifiDataActivityTimeout
+import android.os.Build
+import android.platform.test.annotations.AppModeFull
+import android.provider.Settings
+import android.util.Range
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.SmallTest
+import com.android.net.module.util.ConnectivitySettingsUtils.getPrivateDnsModeAsString
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.time.Duration
+import java.util.Objects
+import kotlin.test.assertFailsWith
+
+/**
+ * Tests for [ConnectivitySettingsManager].
+ *
+ * Build, install and run with:
+ * atest android.net.ConnectivitySettingsManagerTest
+ */
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.R)
+@SmallTest
+@AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
+class ConnectivitySettingsManagerTest {
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context = instrumentation.context
+ private val resolver = context.contentResolver
+
+ private val defaultDuration = Duration.ofSeconds(0L)
+ private val testTime1 = 5L
+ private val testTime2 = 10L
+ private val settingsTypeGlobal = "global"
+ private val settingsTypeSecure = "secure"
+
+ /*** Reset setting value or delete setting if the setting was not existed before testing. */
+ private fun resetSettings(names: Array<String>, type: String, values: Array<String?>) {
+ for (i in names.indices) {
+ if (Objects.equals(values[i], null)) {
+ instrumentation.uiAutomation.executeShellCommand(
+ "settings delete $type ${names[i]}")
+ } else {
+ if (settingsTypeSecure.equals(type)) {
+ Settings.Secure.putString(resolver, names[i], values[i])
+ } else {
+ Settings.Global.putString(resolver, names[i], values[i])
+ }
+ }
+ }
+ }
+
+ fun <T> testIntSetting(
+ names: Array<String>,
+ type: String,
+ value1: T,
+ value2: T,
+ getter: () -> T,
+ setter: (value: T) -> Unit,
+ testIntValues: IntArray
+ ) {
+ val originals: Array<String?> = Array(names.size) { i ->
+ if (settingsTypeSecure.equals(type)) {
+ Settings.Secure.getString(resolver, names[i])
+ } else {
+ Settings.Global.getString(resolver, names[i])
+ }
+ }
+
+ try {
+ for (i in names.indices) {
+ if (settingsTypeSecure.equals(type)) {
+ Settings.Secure.putString(resolver, names[i], testIntValues[i].toString())
+ } else {
+ Settings.Global.putString(resolver, names[i], testIntValues[i].toString())
+ }
+ }
+ assertEquals(value1, getter())
+
+ setter(value2)
+ assertEquals(value2, getter())
+ } finally {
+ resetSettings(names, type, originals)
+ }
+ }
+
+ @Test
+ fun testMobileDataActivityTimeout() {
+ testIntSetting(names = arrayOf(DATA_ACTIVITY_TIMEOUT_MOBILE), type = settingsTypeGlobal,
+ value1 = Duration.ofSeconds(testTime1), value2 = Duration.ofSeconds(testTime2),
+ getter = { getMobileDataActivityTimeout(context, defaultDuration) },
+ setter = { setMobileDataActivityTimeout(context, it) },
+ testIntValues = intArrayOf(testTime1.toInt()))
+ }
+
+ @Test
+ fun testWifiDataActivityTimeout() {
+ testIntSetting(names = arrayOf(DATA_ACTIVITY_TIMEOUT_WIFI), type = settingsTypeGlobal,
+ value1 = Duration.ofSeconds(testTime1), value2 = Duration.ofSeconds(testTime2),
+ getter = { getWifiDataActivityTimeout(context, defaultDuration) },
+ setter = { setWifiDataActivityTimeout(context, it) },
+ testIntValues = intArrayOf(testTime1.toInt()))
+ }
+
+ @Test
+ fun testDnsResolverSampleValidityDuration() {
+ testIntSetting(names = arrayOf(DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS),
+ type = settingsTypeGlobal, value1 = Duration.ofSeconds(testTime1),
+ value2 = Duration.ofSeconds(testTime2),
+ getter = { getDnsResolverSampleValidityDuration(context, defaultDuration) },
+ setter = { setDnsResolverSampleValidityDuration(context, it) },
+ testIntValues = intArrayOf(testTime1.toInt()))
+
+ assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
+ setDnsResolverSampleValidityDuration(context, Duration.ofSeconds(-1L)) }
+ }
+
+ @Test
+ fun testDnsResolverSuccessThresholdPercent() {
+ testIntSetting(names = arrayOf(DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT),
+ type = settingsTypeGlobal, value1 = 5, value2 = 10,
+ getter = { getDnsResolverSuccessThresholdPercent(context, 0 /* def */) },
+ setter = { setDnsResolverSuccessThresholdPercent(context, it) },
+ testIntValues = intArrayOf(5))
+
+ assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
+ setDnsResolverSuccessThresholdPercent(context, -1) }
+ assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
+ setDnsResolverSuccessThresholdPercent(context, 120) }
+ }
+
+ @Test
+ fun testDnsResolverSampleRanges() {
+ testIntSetting(names = arrayOf(DNS_RESOLVER_MIN_SAMPLES, DNS_RESOLVER_MAX_SAMPLES),
+ type = settingsTypeGlobal, value1 = Range(1, 63), value2 = Range(2, 62),
+ getter = { getDnsResolverSampleRanges(context) },
+ setter = { setDnsResolverSampleRanges(context, it) },
+ testIntValues = intArrayOf(1, 63))
+
+ assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
+ setDnsResolverSampleRanges(context, Range(-1, 62)) }
+ assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
+ setDnsResolverSampleRanges(context, Range(2, 65)) }
+ }
+
+ @Test
+ fun testNetworkSwitchNotificationMaximumDailyCount() {
+ testIntSetting(names = arrayOf(NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT),
+ type = settingsTypeGlobal, value1 = 5, value2 = 15,
+ getter = { getNetworkSwitchNotificationMaximumDailyCount(context, 0 /* def */) },
+ setter = { setNetworkSwitchNotificationMaximumDailyCount(context, it) },
+ testIntValues = intArrayOf(5))
+
+ assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
+ setNetworkSwitchNotificationMaximumDailyCount(context, -1) }
+ }
+
+ @Test
+ fun testNetworkSwitchNotificationRateDuration() {
+ testIntSetting(names = arrayOf(NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS),
+ type = settingsTypeGlobal, value1 = Duration.ofMillis(testTime1),
+ value2 = Duration.ofMillis(testTime2),
+ getter = { getNetworkSwitchNotificationRateDuration(context, defaultDuration) },
+ setter = { setNetworkSwitchNotificationRateDuration(context, it) },
+ testIntValues = intArrayOf(testTime1.toInt()))
+
+ assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
+ setNetworkSwitchNotificationRateDuration(context, Duration.ofMillis(-1L)) }
+ }
+
+ @Test
+ fun testCaptivePortalMode() {
+ testIntSetting(names = arrayOf(CAPTIVE_PORTAL_MODE), type = settingsTypeGlobal,
+ value1 = CAPTIVE_PORTAL_MODE_AVOID, value2 = CAPTIVE_PORTAL_MODE_PROMPT,
+ getter = { getCaptivePortalMode(context, CAPTIVE_PORTAL_MODE_IGNORE) },
+ setter = { setCaptivePortalMode(context, it) },
+ testIntValues = intArrayOf(CAPTIVE_PORTAL_MODE_AVOID))
+
+ assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
+ setCaptivePortalMode(context, 5 /* mode */) }
+ }
+
+ @Test
+ fun testPrivateDnsDefaultMode() {
+ val original = Settings.Global.getString(resolver, PRIVATE_DNS_DEFAULT_MODE)
+
+ try {
+ val mode = getPrivateDnsModeAsString(PRIVATE_DNS_MODE_OPPORTUNISTIC)
+ Settings.Global.putString(resolver, PRIVATE_DNS_DEFAULT_MODE, mode)
+ assertEquals(mode, getPrivateDnsDefaultMode(context))
+
+ setPrivateDnsDefaultMode(context, PRIVATE_DNS_MODE_OFF)
+ assertEquals(getPrivateDnsModeAsString(PRIVATE_DNS_MODE_OFF),
+ getPrivateDnsDefaultMode(context))
+ } finally {
+ resetSettings(names = arrayOf(PRIVATE_DNS_DEFAULT_MODE), type = settingsTypeGlobal,
+ values = arrayOf(original))
+ }
+
+ assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
+ setPrivateDnsDefaultMode(context, -1) }
+ }
+
+ @Test
+ fun testConnectivityKeepPendingIntentDuration() {
+ testIntSetting(names = arrayOf(CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS),
+ type = settingsTypeSecure, value1 = Duration.ofMillis(testTime1),
+ value2 = Duration.ofMillis(testTime2),
+ getter = { getConnectivityKeepPendingIntentDuration(context, defaultDuration) },
+ setter = { setConnectivityKeepPendingIntentDuration(context, it) },
+ testIntValues = intArrayOf(testTime1.toInt()))
+
+ assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
+ setConnectivityKeepPendingIntentDuration(context, Duration.ofMillis(-1L)) }
+ }
+
+ @Test
+ fun testMobileDataAlwaysOn() {
+ testIntSetting(names = arrayOf(MOBILE_DATA_ALWAYS_ON), type = settingsTypeGlobal,
+ value1 = false, value2 = true,
+ getter = { getMobileDataAlwaysOn(context, true /* def */) },
+ setter = { setMobileDataAlwaysOn(context, it) },
+ testIntValues = intArrayOf(0))
+ }
+
+ @Test
+ fun testWifiAlwaysRequested() {
+ testIntSetting(names = arrayOf(WIFI_ALWAYS_REQUESTED), type = settingsTypeGlobal,
+ value1 = false, value2 = true,
+ getter = { getWifiAlwaysRequested(context, true /* def */) },
+ setter = { setWifiAlwaysRequested(context, it) },
+ testIntValues = intArrayOf(0))
+ }
+}
\ No newline at end of file
diff --git a/tests/common/java/android/net/InvalidPacketExceptionTest.kt b/tests/common/java/android/net/InvalidPacketExceptionTest.kt
new file mode 100644
index 0000000..320ac27
--- /dev/null
+++ b/tests/common/java/android/net/InvalidPacketExceptionTest.kt
@@ -0,0 +1,35 @@
+/*
+ * 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
+
+import android.os.Build
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import org.junit.runner.RunWith
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
+class InvalidPacketExceptionTest {
+ @Test
+ fun testConstructor() {
+ assertEquals(123, InvalidPacketException(123).error)
+ assertEquals(0, InvalidPacketException(0).error)
+ assertEquals(-123, InvalidPacketException(-123).error)
+ }
+}
\ No newline at end of file
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 8b57a92..8f471c1 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -29,6 +29,8 @@
import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.ConnectivityManager.EXTRA_NETWORK;
+import static android.net.ConnectivityManager.EXTRA_NETWORK_REQUEST;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
@@ -53,16 +55,12 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-import static android.net.TetheringManager.TETHERING_WIFI;
-import static android.net.TetheringManager.TetheringRequest;
import static android.net.cts.util.CtsNetUtils.ConnectivityActionReceiver;
import static android.net.cts.util.CtsNetUtils.HTTP_PORT;
import static android.net.cts.util.CtsNetUtils.NETWORK_CALLBACK_ACTION;
import static android.net.cts.util.CtsNetUtils.TEST_HOST;
import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
-import static android.net.cts.util.CtsTetheringUtils.StartTetheringCallback;
import static android.net.cts.util.CtsTetheringUtils.TestTetheringEventCallback;
-import static android.net.cts.util.CtsTetheringUtils.isWifiTetheringSupported;
import static android.net.util.NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTPS_URL;
import static android.net.util.NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTP_URL;
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
@@ -80,6 +78,7 @@
import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
import static com.android.testutils.TestPermissionUtil.runAsShell;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -111,11 +110,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;
@@ -125,9 +128,9 @@
import android.net.TelephonyNetworkSpecifier;
import android.net.TestNetworkInterface;
import android.net.TestNetworkManager;
-import android.net.TetheringManager;
import android.net.Uri;
import android.net.cts.util.CtsNetUtils;
+import android.net.cts.util.CtsTetheringUtils;
import android.net.util.KeepaliveUtils;
import android.net.wifi.WifiManager;
import android.os.Binder;
@@ -202,9 +205,12 @@
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;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -239,6 +245,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;
@@ -271,7 +280,6 @@
private final ArraySet<Integer> mNetworkTypes = new ArraySet<>();
private UiAutomation mUiAutomation;
private CtsNetUtils mCtsNetUtils;
- private TetheringManager mTm;
// Used for cleanup purposes.
private final List<Range<Integer>> mVpnRequiredUidRanges = new ArrayList<>();
@@ -287,7 +295,6 @@
mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
mPackageManager = mContext.getPackageManager();
mCtsNetUtils = new CtsNetUtils(mContext);
- mTm = mContext.getSystemService(TetheringManager.class);
if (DevSdkIgnoreRuleKt.isDevSdkInRange(null /* minExclusive */,
Build.VERSION_CODES.R /* maxInclusive */)) {
@@ -522,8 +529,10 @@
Objects.requireNonNull(mCm.getNetworkCapabilities(network));
// Redact specifier of the capabilities of the snapshot before comparing since
// the result returned from getNetworkCapabilities always get redacted.
+ final NetworkSpecifier snapshotCapSpecifier =
+ snapshot.getNetworkCapabilities().getNetworkSpecifier();
final NetworkSpecifier redactedSnapshotCapSpecifier =
- snapshot.getNetworkCapabilities().getNetworkSpecifier().redact();
+ snapshotCapSpecifier == null ? null : snapshotCapSpecifier.redact();
assertEquals("", caps.describeImmutableDifferences(
snapshot.getNetworkCapabilities()
.setNetworkSpecifier(redactedSnapshotCapSpecifier)));
@@ -697,6 +706,7 @@
.build();
}
+ @AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
public void testIsPrivateDnsBroken() throws InterruptedException {
final String invalidPrivateDnsServer = "invalidhostname.example.com";
@@ -847,6 +857,117 @@
}
}
+ private void runIdenticalPendingIntentsRequestTest(boolean useListen) throws Exception {
+ assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
+
+ // Disconnect before registering callbacks, reconnect later to fire them
+ mCtsNetUtils.ensureWifiDisconnected(null);
+
+ final NetworkRequest firstRequest = makeWifiNetworkRequest();
+ final NetworkRequest secondRequest = new NetworkRequest(firstRequest);
+ // Will match wifi or test, since transports are ORed; but there should only be wifi
+ secondRequest.networkCapabilities.addTransportType(TRANSPORT_TEST);
+
+ PendingIntent firstIntent = null;
+ PendingIntent secondIntent = null;
+ BroadcastReceiver receiver = null;
+
+ // Avoid receiving broadcasts from other runs by appending a timestamp
+ final String broadcastAction = NETWORK_CALLBACK_ACTION + System.currentTimeMillis();
+ try {
+ // TODO: replace with PendingIntent.FLAG_MUTABLE when this code compiles against S+
+ // Intent is mutable to receive EXTRA_NETWORK_REQUEST from ConnectivityService
+ final int pendingIntentFlagMutable = 1 << 25;
+ final String extraBoolKey = "extra_bool";
+ firstIntent = PendingIntent.getBroadcast(mContext,
+ 0 /* requestCode */,
+ new Intent(broadcastAction).putExtra(extraBoolKey, false),
+ PendingIntent.FLAG_UPDATE_CURRENT | pendingIntentFlagMutable);
+
+ if (useListen) {
+ mCm.registerNetworkCallback(firstRequest, firstIntent);
+ } else {
+ mCm.requestNetwork(firstRequest, firstIntent);
+ }
+
+ // Second intent equals the first as per filterEquals (extras don't count), so first
+ // intent will be updated with the new extras
+ secondIntent = PendingIntent.getBroadcast(mContext,
+ 0 /* requestCode */,
+ new Intent(broadcastAction).putExtra(extraBoolKey, true),
+ PendingIntent.FLAG_UPDATE_CURRENT | pendingIntentFlagMutable);
+
+ // Because secondIntent.intentFilterEquals the first, the request should be replaced
+ if (useListen) {
+ mCm.registerNetworkCallback(secondRequest, secondIntent);
+ } else {
+ mCm.requestNetwork(secondRequest, secondIntent);
+ }
+
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(broadcastAction);
+
+ final CompletableFuture<Network> networkFuture = new CompletableFuture<>();
+ final AtomicInteger receivedCount = new AtomicInteger(0);
+ receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final NetworkRequest request = intent.getParcelableExtra(EXTRA_NETWORK_REQUEST);
+ assertPendingIntentRequestMatches(request, secondRequest, useListen);
+ receivedCount.incrementAndGet();
+ networkFuture.complete(intent.getParcelableExtra(EXTRA_NETWORK));
+ }
+ };
+ mContext.registerReceiver(receiver, filter);
+
+ final Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
+ try {
+ assertEquals(wifiNetwork, networkFuture.get(
+ NETWORK_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } catch (TimeoutException e) {
+ throw new AssertionError("PendingIntent not received for " + secondRequest, e);
+ }
+
+ // Sleep for a small amount of time to try to check that only one callback is ever
+ // received (so the first callback was really unregistered). This does not guarantee
+ // that the test will fail if it runs very slowly, but it should at least be very
+ // noticeably flaky.
+ Thread.sleep(NO_CALLBACK_TIMEOUT_MS);
+
+ // TODO: BUG (b/189868426): this should also apply to listens
+ if (!useListen) {
+ assertEquals("PendingIntent should only be received once", 1, receivedCount.get());
+ }
+ } finally {
+ if (firstIntent != null) mCm.unregisterNetworkCallback(firstIntent);
+ if (secondIntent != null) mCm.unregisterNetworkCallback(secondIntent);
+ if (receiver != null) mContext.unregisterReceiver(receiver);
+ mCtsNetUtils.ensureWifiConnected();
+ }
+ }
+
+ private void assertPendingIntentRequestMatches(NetworkRequest broadcasted, NetworkRequest filed,
+ boolean useListen) {
+ assertArrayEquals(filed.networkCapabilities.getCapabilities(),
+ broadcasted.networkCapabilities.getCapabilities());
+ // TODO: BUG (b/189868426): this should also apply to listens
+ if (useListen) return;
+ assertArrayEquals(filed.networkCapabilities.getTransportTypes(),
+ broadcasted.networkCapabilities.getTransportTypes());
+ }
+
+ @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @Test
+ public void testRegisterNetworkRequest_identicalPendingIntents() throws Exception {
+ runIdenticalPendingIntentsRequestTest(false /* useListen */);
+ }
+
+ @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @Test
+ public void testRegisterNetworkCallback_identicalPendingIntents() throws Exception {
+ runIdenticalPendingIntentsRequestTest(true /* useListen */);
+ }
+
/**
* Exercises the requestNetwork with NetworkCallback API. This checks to
* see if we get a callback for an INTERNET request.
@@ -2104,14 +2225,15 @@
ConnectivitySettingsManager.getNetworkAvoidBadWifi(mContext);
final int curPrivateDnsMode = ConnectivitySettingsManager.getPrivateDnsMode(mContext);
- final TestTetheringEventCallback tetherEventCallback = new TestTetheringEventCallback();
+ TestTetheringEventCallback tetherEventCallback = null;
+ final CtsTetheringUtils tetherUtils = new CtsTetheringUtils(mContext);
try {
- mTm.registerTetheringEventCallback(c -> c.run() /* executor */, tetherEventCallback);
+ tetherEventCallback = tetherUtils.registerTetheringEventCallback();
// Adopt for NETWORK_SETTINGS permission.
mUiAutomation.adoptShellPermissionIdentity();
// start tethering
tetherEventCallback.assumeWifiTetheringSupported(mContext);
- startWifiTethering(tetherEventCallback);
+ tetherUtils.startWifiTethering(tetherEventCallback);
// Update setting to verify the behavior.
mCm.setAirplaneMode(true);
ConnectivitySettingsManager.setPrivateDnsMode(mContext,
@@ -2132,8 +2254,10 @@
mCm.setAirplaneMode(false);
ConnectivitySettingsManager.setNetworkAvoidBadWifi(mContext, curAvoidBadWifi);
ConnectivitySettingsManager.setPrivateDnsMode(mContext, curPrivateDnsMode);
- mTm.unregisterTetheringEventCallback(tetherEventCallback);
- mTm.stopAllTethering();
+ if (tetherEventCallback != null) {
+ tetherUtils.unregisterTetheringEventCallback(tetherEventCallback);
+ }
+ tetherUtils.stopAllTethering();
mUiAutomation.dropShellPermissionIdentity();
}
}
@@ -2180,25 +2304,12 @@
ConnectivitySettingsManager.getNetworkAvoidBadWifi(mContext));
}
- private void startWifiTethering(final TestTetheringEventCallback callback) throws Exception {
- if (!isWifiTetheringSupported(mContext, callback)) return;
-
- final List<String> wifiRegexs =
- callback.getTetheringInterfaceRegexps().getTetherableWifiRegexs();
- final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
- final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
- .setShouldShowEntitlementUi(false).build();
- mTm.startTethering(request, c -> c.run() /* executor */, startTetheringCallback);
- startTetheringCallback.verifyTetheringStarted();
- callback.expectTetheredInterfacesChanged(wifiRegexs, TETHERING_WIFI);
- }
-
/**
* Verify that per-app OEM network preference functions as expected for network preference TEST.
* For specified apps, validate networks are prioritized in order: unmetered, TEST transport,
* default network.
*/
- @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @AppModeFull(reason = "Instant apps cannot create test networks")
@Test
public void testSetOemNetworkPreferenceForTestPref() throws Exception {
// Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
@@ -2258,6 +2369,7 @@
* Verify that per-app OEM network preference functions as expected for network pref TEST_ONLY.
* For specified apps, validate that only TEST transport type networks are used.
*/
+ @AppModeFull(reason = "Instant apps cannot create test networks")
@Test
public void testSetOemNetworkPreferenceForTestOnlyPref() throws Exception {
// Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
@@ -2393,8 +2505,7 @@
} finally {
resetValidationConfig();
// Reconnect wifi to reset the wifi status
- mCtsNetUtils.ensureWifiDisconnected(null /* wifiNetworkToCheck */);
- mCtsNetUtils.ensureWifiConnected();
+ reconnectWifi();
}
}
@@ -2469,6 +2580,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();
@@ -2508,6 +2701,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
@@ -2627,4 +2835,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);
+ }
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/IpSecBaseTest.java b/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
index c54ee91..7f710d7 100644
--- a/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
@@ -23,6 +23,7 @@
import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA384;
import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA512;
import static android.net.IpSecAlgorithm.CRYPT_AES_CBC;
+import static android.system.OsConstants.FIONREAD;
import static org.junit.Assert.assertArrayEquals;
@@ -32,8 +33,10 @@
import android.net.IpSecManager;
import android.net.IpSecTransform;
import android.platform.test.annotations.AppModeFull;
+import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
+import android.system.StructTimeval;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
@@ -46,15 +49,21 @@
import org.junit.runner.RunWith;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
-import java.net.ServerSocket;
import java.net.Socket;
+import java.net.SocketAddress;
import java.net.SocketException;
+import java.net.SocketImpl;
+import java.net.SocketOptions;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@@ -232,6 +241,12 @@
public NativeTcpSocket(FileDescriptor fd) {
super(fd);
}
+
+ public JavaTcpSocket acceptToJavaSocket() throws Exception {
+ InetSocketAddress peer = new InetSocketAddress(0);
+ FileDescriptor newFd = Os.accept(mFd, peer);
+ return new JavaTcpSocket(new AcceptedTcpFileDescriptorSocket(newFd, peer, getPort()));
+ }
}
public static class NativeUdpSocket extends NativeSocket implements GenericUdpSocket {
@@ -357,6 +372,137 @@
}
}
+ private static class AcceptedTcpFileDescriptorSocket extends Socket {
+
+ AcceptedTcpFileDescriptorSocket(FileDescriptor fd, InetSocketAddress remote,
+ int localPort) throws IOException {
+ super(new FileDescriptorSocketImpl(fd, remote, localPort));
+ connect(remote);
+ }
+
+ private static class FileDescriptorSocketImpl extends SocketImpl {
+
+ private FileDescriptorSocketImpl(FileDescriptor fd, InetSocketAddress remote,
+ int localPort) {
+ this.fd = fd;
+ this.address = remote.getAddress();
+ this.port = remote.getPort();
+ this.localport = localPort;
+ }
+
+ @Override
+ protected void create(boolean stream) throws IOException {
+ // The socket has been created.
+ }
+
+ @Override
+ protected void connect(String host, int port) throws IOException {
+ // The socket has connected.
+ }
+
+ @Override
+ protected void connect(InetAddress address, int port) throws IOException {
+ // The socket has connected.
+ }
+
+ @Override
+ protected void connect(SocketAddress address, int timeout) throws IOException {
+ // The socket has connected.
+ }
+
+ @Override
+ protected void bind(InetAddress host, int port) throws IOException {
+ // The socket is bounded.
+ }
+
+ @Override
+ protected void listen(int backlog) throws IOException {
+ throw new UnsupportedOperationException("listen");
+ }
+
+ @Override
+ protected void accept(SocketImpl s) throws IOException {
+ throw new UnsupportedOperationException("accept");
+ }
+
+ @Override
+ protected InputStream getInputStream() throws IOException {
+ return new FileInputStream(fd);
+ }
+
+ @Override
+ protected OutputStream getOutputStream() throws IOException {
+ return new FileOutputStream(fd);
+ }
+
+ @Override
+ protected int available() throws IOException {
+ try {
+ return Os.ioctlInt(fd, FIONREAD);
+ } catch (ErrnoException e) {
+ throw new IOException(e);
+ }
+ }
+
+ @Override
+ protected void close() throws IOException {
+ try {
+ Os.close(fd);
+ } catch (ErrnoException e) {
+ throw new IOException(e);
+ }
+ }
+
+ @Override
+ protected void sendUrgentData(int data) throws IOException {
+ throw new UnsupportedOperationException("sendUrgentData");
+ }
+
+ @Override
+ public void setOption(int optID, Object value) throws SocketException {
+ try {
+ setOptionInternal(optID, value);
+ } catch (ErrnoException e) {
+ throw new SocketException(e.getMessage());
+ }
+ }
+
+ private void setOptionInternal(int optID, Object value) throws ErrnoException,
+ SocketException {
+ switch(optID) {
+ case SocketOptions.SO_TIMEOUT:
+ int millis = (Integer) value;
+ StructTimeval tv = StructTimeval.fromMillis(millis);
+ Os.setsockoptTimeval(fd, OsConstants.SOL_SOCKET, OsConstants.SO_RCVTIMEO,
+ tv);
+ return;
+ default:
+ throw new SocketException("Unknown socket option: " + optID);
+ }
+ }
+
+ @Override
+ public Object getOption(int optID) throws SocketException {
+ try {
+ return getOptionInternal(optID);
+ } catch (ErrnoException e) {
+ throw new SocketException(e.getMessage());
+ }
+ }
+
+ private Object getOptionInternal(int optID) throws ErrnoException, SocketException {
+ switch (optID) {
+ case SocketOptions.SO_LINGER:
+ // Returns an arbitrary value because IpSecManager doesn't actually
+ // use this value.
+ return 10;
+ default:
+ throw new SocketException("Unknown socket option: " + optID);
+ }
+ }
+ }
+ }
+
public static class SocketPair<T> {
public final T mLeftSock;
public final T mRightSock;
@@ -441,8 +587,6 @@
public static SocketPair<JavaTcpSocket> getJavaTcpSocketPair(
InetAddress localAddr, IpSecManager ism, IpSecTransform transform) throws Exception {
JavaTcpSocket clientSock = new JavaTcpSocket(new Socket());
- ServerSocket serverSocket = new ServerSocket();
- serverSocket.bind(new InetSocketAddress(localAddr, 0));
// While technically the client socket does not need to be bound, the OpenJDK implementation
// of Socket only allocates an FD when bind() or connect() or other similar methods are
@@ -451,16 +595,19 @@
clientSock.mSocket.bind(new InetSocketAddress(localAddr, 0));
// IpSecService doesn't support serverSockets at the moment; workaround using FD
- FileDescriptor serverFd = serverSocket.getImpl().getFD$();
+ NativeTcpSocket server = new NativeTcpSocket(
+ Os.socket(getDomain(localAddr), OsConstants.SOCK_STREAM, OsConstants.IPPROTO_TCP));
+ Os.bind(server.mFd, localAddr, 0);
- applyTransformBidirectionally(ism, transform, new NativeTcpSocket(serverFd));
+ applyTransformBidirectionally(ism, transform, server);
applyTransformBidirectionally(ism, transform, clientSock);
- clientSock.mSocket.connect(new InetSocketAddress(localAddr, serverSocket.getLocalPort()));
- JavaTcpSocket acceptedSock = new JavaTcpSocket(serverSocket.accept());
+ Os.listen(server.mFd, 10 /* backlog */);
+ clientSock.mSocket.connect(new InetSocketAddress(localAddr, server.getPort()));
+ JavaTcpSocket acceptedSock = server.acceptToJavaSocket();
applyTransformBidirectionally(ism, transform, acceptedSock);
- serverSocket.close();
+ server.close();
return new SocketPair<>(clientSock, acceptedSock);
}
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
index ae38faa..a9a3380 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
@@ -43,6 +43,7 @@
import android.net.ConnectivityManager;
import android.net.IpSecAlgorithm;
import android.net.IpSecManager;
+import android.net.IpSecManager.IpSecTunnelInterface;
import android.net.IpSecTransform;
import android.net.LinkAddress;
import android.net.Network;
@@ -50,25 +51,33 @@
import android.net.TestNetworkManager;
import android.net.cts.PacketUtils.Payload;
import android.net.cts.util.CtsNetUtils;
+import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.AppModeFull;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.NetworkInterface;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+
@RunWith(AndroidJUnit4.class)
@AppModeFull(reason = "MANAGE_TEST_NETWORKS permission can't be granted to instant apps")
public class IpSecManagerTunnelTest extends IpSecBaseTest {
+ @Rule public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
private static final String TAG = IpSecManagerTunnelTest.class.getSimpleName();
private static final InetAddress LOCAL_OUTER_4 = InetAddress.parseNumericAddress("192.0.2.1");
@@ -78,6 +87,15 @@
private static final InetAddress REMOTE_OUTER_6 =
InetAddress.parseNumericAddress("2001:db8:1::2");
+ private static final InetAddress LOCAL_OUTER_4_NEW =
+ InetAddress.parseNumericAddress("192.0.2.101");
+ private static final InetAddress REMOTE_OUTER_4_NEW =
+ InetAddress.parseNumericAddress("192.0.2.102");
+ private static final InetAddress LOCAL_OUTER_6_NEW =
+ InetAddress.parseNumericAddress("2001:db8:1::101");
+ private static final InetAddress REMOTE_OUTER_6_NEW =
+ InetAddress.parseNumericAddress("2001:db8:1::102");
+
private static final InetAddress LOCAL_INNER_4 =
InetAddress.parseNumericAddress("198.51.100.1");
private static final InetAddress REMOTE_INNER_4 =
@@ -95,10 +113,9 @@
// Static state to reduce setup/teardown
private static ConnectivityManager sCM;
private static TestNetworkManager sTNM;
- private static ParcelFileDescriptor sTunFd;
- private static TestNetworkCallback sTunNetworkCallback;
- private static Network sTunNetwork;
- private static TunUtils sTunUtils;
+
+ private static TunNetworkWrapper sTunWrapper;
+ private static TunNetworkWrapper sTunWrapperNew;
private static Context sContext = InstrumentationRegistry.getContext();
private static final CtsNetUtils mCtsNetUtils = new CtsNetUtils(sContext);
@@ -116,19 +133,8 @@
// right appop permissions.
mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, true);
- TestNetworkInterface testIface =
- sTNM.createTunInterface(
- new LinkAddress[] {
- new LinkAddress(LOCAL_OUTER_4, IP4_PREFIX_LEN),
- new LinkAddress(LOCAL_OUTER_6, IP6_PREFIX_LEN)
- });
-
- sTunFd = testIface.getFileDescriptor();
- sTunNetworkCallback = mCtsNetUtils.setupAndGetTestNetwork(testIface.getInterfaceName());
- sTunNetworkCallback.waitForAvailable();
- sTunNetwork = sTunNetworkCallback.currentNetwork;
-
- sTunUtils = new TunUtils(sTunFd);
+ sTunWrapper = new TunNetworkWrapper(LOCAL_OUTER_4, LOCAL_OUTER_6);
+ sTunWrapperNew = new TunNetworkWrapper(LOCAL_OUTER_4_NEW, LOCAL_OUTER_6_NEW);
}
@Before
@@ -139,24 +145,76 @@
// Set to true before every run; some tests flip this.
mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, true);
- // Clear sTunUtils state
- sTunUtils.reset();
+ // Clear TunUtils state
+ sTunWrapper.utils.reset();
+ sTunWrapperNew.utils.reset();
+ }
+
+ private static void tearDownTunWrapperIfNotNull(TunNetworkWrapper tunWrapper) throws Exception {
+ if (tunWrapper != null) {
+ tunWrapper.tearDown();
+ }
}
@AfterClass
public static void tearDownAfterClass() throws Exception {
mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, false);
- sCM.unregisterNetworkCallback(sTunNetworkCallback);
-
- sTNM.teardownTestNetwork(sTunNetwork);
- sTunFd.close();
+ tearDownTunWrapperIfNotNull(sTunWrapper);
+ tearDownTunWrapperIfNotNull(sTunWrapperNew);
InstrumentationRegistry.getInstrumentation()
.getUiAutomation()
.dropShellPermissionIdentity();
}
+ private static class TunNetworkWrapper {
+ public final ParcelFileDescriptor fd;
+ public final TestNetworkCallback networkCallback;
+ public final Network network;
+ public final TunUtils utils;
+
+ TunNetworkWrapper(InetAddress... addresses) throws Exception {
+ final LinkAddress[] linkAddresses = new LinkAddress[addresses.length];
+ for (int i = 0; i < linkAddresses.length; i++) {
+ InetAddress addr = addresses[i];
+ if (addr instanceof Inet4Address) {
+ linkAddresses[i] = new LinkAddress(addr, IP4_PREFIX_LEN);
+ } else {
+ linkAddresses[i] = new LinkAddress(addr, IP6_PREFIX_LEN);
+ }
+ }
+
+ try {
+ final TestNetworkInterface testIface = sTNM.createTunInterface(linkAddresses);
+
+ fd = testIface.getFileDescriptor();
+ networkCallback = mCtsNetUtils.setupAndGetTestNetwork(testIface.getInterfaceName());
+ networkCallback.waitForAvailable();
+ network = networkCallback.currentNetwork;
+ } catch (Exception e) {
+ tearDown();
+ throw e;
+ }
+
+ utils = new TunUtils(fd);
+ }
+
+ public void tearDown() throws Exception {
+ if (networkCallback != null) {
+ sCM.unregisterNetworkCallback(networkCallback);
+ }
+
+ if (network != null) {
+ sTNM.teardownTestNetwork(network);
+ }
+
+ if (fd != null) {
+ fd.close();
+ }
+ }
+ }
+
@Test
public void testSecurityExceptionCreateTunnelInterfaceWithoutAppop() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -166,7 +224,7 @@
// Security exceptions are thrown regardless of IPv4/IPv6. Just test one
try {
- mISM.createIpSecTunnelInterface(LOCAL_INNER_6, REMOTE_INNER_6, sTunNetwork);
+ mISM.createIpSecTunnelInterface(LOCAL_INNER_6, REMOTE_INNER_6, sTunWrapper.network);
fail("Did not throw SecurityException for Tunnel creation without appop");
} catch (SecurityException expected) {
}
@@ -196,11 +254,16 @@
* Runs the test code, and returns the inner socket port, if any.
*
* @param ipsecNetwork The IPsec Interface based Network for binding sockets on
+ * @param tunnelIface The IPsec tunnel interface that will be tested
+ * @param underlyingTunUtils The utility of the IPsec tunnel interface's underlying TUN
+ * network
* @return the integer port of the inner socket if outbound, or 0 if inbound
* IpSecTunnelTestRunnable
* @throws Exception if any part of the test failed.
*/
- public abstract int run(Network ipsecNetwork) throws Exception;
+ public abstract int run(
+ Network ipsecNetwork, IpSecTunnelInterface tunnelIface, TunUtils underlyingTunUtils)
+ throws Exception;
}
private int getPacketSize(
@@ -265,7 +328,9 @@
int expectedPacketSize) {
return new IpSecTunnelTestRunnable() {
@Override
- public int run(Network ipsecNetwork) throws Exception {
+ public int run(
+ Network ipsecNetwork, IpSecTunnelInterface tunnelIface, TunUtils tunUtils)
+ throws Exception {
// Build a socket and send traffic
JavaUdpSocket socket = new JavaUdpSocket(localInner);
ipsecNetwork.bindSocket(socket.mSocket);
@@ -284,7 +349,7 @@
// Verify that an encrypted packet is sent. As of right now, checking encrypted
// body is not possible, due to the test not knowing some of the fields of the
// inner IP header (flow label, flags, etc)
- sTunUtils.awaitEspPacketNoPlaintext(
+ tunUtils.awaitEspPacketNoPlaintext(
spi, TEST_DATA, encapPort != 0, expectedPacketSize);
socket.close();
@@ -312,7 +377,9 @@
throws Exception {
return new IpSecTunnelTestRunnable() {
@Override
- public int run(Network ipsecNetwork) throws Exception {
+ public int run(
+ Network ipsecNetwork, IpSecTunnelInterface tunnelIface, TunUtils tunUtils)
+ throws Exception {
// Build a socket and receive traffic
JavaUdpSocket socket = new JavaUdpSocket(localInner, innerSocketPort);
ipsecNetwork.bindSocket(socket.mSocket);
@@ -325,7 +392,7 @@
socket.mSocket, IpSecManager.DIRECTION_OUT, inTransportTransform);
}
- sTunUtils.reflectPackets();
+ tunUtils.reflectPackets();
// Receive packet from socket, and validate that the payload is correct
receiveAndValidatePacket(socket);
@@ -355,7 +422,9 @@
throws Exception {
return new IpSecTunnelTestRunnable() {
@Override
- public int run(Network ipsecNetwork) throws Exception {
+ public int run(
+ Network ipsecNetwork, IpSecTunnelInterface tunnelIface, TunUtils tunUtils)
+ throws Exception {
// Build a socket and receive traffic
JavaUdpSocket socket = new JavaUdpSocket(localInner);
ipsecNetwork.bindSocket(socket.mSocket);
@@ -391,7 +460,7 @@
socket.getPort(),
encapPort);
}
- sTunUtils.injectPacket(pkt);
+ tunUtils.injectPacket(pkt);
// Receive packet from socket, and validate
receiveAndValidatePacket(socket);
@@ -404,6 +473,161 @@
}
}
+ private class MigrateIpSecTunnelTestRunnableFactory implements IpSecTunnelTestRunnableFactory {
+ private final IpSecTunnelTestRunnableFactory mTestRunnableFactory;
+
+ MigrateIpSecTunnelTestRunnableFactory(boolean isOutputTest) {
+ if (isOutputTest) {
+ mTestRunnableFactory = new OutputIpSecTunnelTestRunnableFactory();
+ } else {
+ mTestRunnableFactory = new InputPacketGeneratorIpSecTunnelTestRunnableFactory();
+ }
+ }
+
+ @Override
+ public IpSecTunnelTestRunnable getIpSecTunnelTestRunnable(
+ boolean transportInTunnelMode,
+ int spi,
+ InetAddress localInner,
+ InetAddress remoteInner,
+ InetAddress localOuter,
+ InetAddress remoteOuter,
+ IpSecTransform inTransportTransform,
+ IpSecTransform outTransportTransform,
+ int encapPort,
+ int unusedInnerSocketPort,
+ int expectedPacketSize) {
+ return new IpSecTunnelTestRunnable() {
+ @Override
+ public int run(
+ Network ipsecNetwork, IpSecTunnelInterface tunnelIface, TunUtils tunUtils)
+ throws Exception {
+ mTestRunnableFactory
+ .getIpSecTunnelTestRunnable(
+ transportInTunnelMode,
+ spi,
+ localInner,
+ remoteInner,
+ localOuter,
+ remoteOuter,
+ inTransportTransform,
+ outTransportTransform,
+ encapPort,
+ unusedInnerSocketPort,
+ expectedPacketSize)
+ .run(ipsecNetwork, tunnelIface, sTunWrapper.utils);
+
+ tunnelIface.setUnderlyingNetwork(sTunWrapperNew.network);
+
+ // Verify migrating to IPv4 and IPv6 addresses. It ensures that not only
+ // can IPsec tunnel migrate across interfaces, IPsec tunnel can also migrate to
+ // a different address on the same interface.
+ checkMigratedTunnel(
+ localInner,
+ remoteInner,
+ LOCAL_OUTER_4_NEW,
+ REMOTE_OUTER_4_NEW,
+ encapPort != 0,
+ transportInTunnelMode,
+ sTunWrapperNew.utils,
+ tunnelIface,
+ ipsecNetwork);
+ checkMigratedTunnel(
+ localInner,
+ remoteInner,
+ LOCAL_OUTER_6_NEW,
+ REMOTE_OUTER_6_NEW,
+ false, // IPv6 does not support UDP encapsulation
+ transportInTunnelMode,
+ sTunWrapperNew.utils,
+ tunnelIface,
+ ipsecNetwork);
+
+ return 0;
+ }
+ };
+ }
+
+ private void checkMigratedTunnel(
+ InetAddress localInner,
+ InetAddress remoteInner,
+ InetAddress localOuter,
+ InetAddress remoteOuter,
+ boolean useEncap,
+ boolean transportInTunnelMode,
+ TunUtils tunUtils,
+ IpSecTunnelInterface tunnelIface,
+ Network ipsecNetwork)
+ throws Exception {
+
+ // Preselect both SPI and encap port, to be used for both inbound and outbound tunnels.
+ // Re-uses the same SPI to ensure that even in cases of symmetric SPIs shared across
+ // tunnel and transport mode, packets are encrypted/decrypted properly based on the
+ // src/dst.
+ int spi = getRandomSpi(localOuter, remoteOuter);
+
+ int innerFamily = localInner instanceof Inet4Address ? AF_INET : AF_INET6;
+ int outerFamily = localOuter instanceof Inet4Address ? AF_INET : AF_INET6;
+ int expectedPacketSize =
+ getPacketSize(innerFamily, outerFamily, useEncap, transportInTunnelMode);
+
+ // Build transport mode transforms and encapsulation socket for verifying
+ // transport-in-tunnel case and encapsulation case.
+ try (IpSecManager.SecurityParameterIndex inTransportSpi =
+ mISM.allocateSecurityParameterIndex(localInner, spi);
+ IpSecManager.SecurityParameterIndex outTransportSpi =
+ mISM.allocateSecurityParameterIndex(remoteInner, spi);
+ IpSecTransform inTransportTransform =
+ buildIpSecTransform(sContext, inTransportSpi, null, remoteInner);
+ IpSecTransform outTransportTransform =
+ buildIpSecTransform(sContext, outTransportSpi, null, localInner);
+ UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
+
+ // Configure tunnel mode Transform parameters
+ IpSecTransform.Builder transformBuilder = new IpSecTransform.Builder(sContext);
+ transformBuilder.setEncryption(
+ new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY));
+ transformBuilder.setAuthentication(
+ new IpSecAlgorithm(
+ IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4));
+
+ if (useEncap) {
+ transformBuilder.setIpv4Encapsulation(encapSocket, encapSocket.getPort());
+ }
+
+ // Apply transform and check that traffic is properly encrypted
+ try (IpSecManager.SecurityParameterIndex inSpi =
+ mISM.allocateSecurityParameterIndex(localOuter, spi);
+ IpSecManager.SecurityParameterIndex outSpi =
+ mISM.allocateSecurityParameterIndex(remoteOuter, spi);
+ IpSecTransform inTransform =
+ transformBuilder.buildTunnelModeTransform(remoteOuter, inSpi);
+ IpSecTransform outTransform =
+ transformBuilder.buildTunnelModeTransform(localOuter, outSpi)) {
+ mISM.applyTunnelModeTransform(
+ tunnelIface, IpSecManager.DIRECTION_IN, inTransform);
+ mISM.applyTunnelModeTransform(
+ tunnelIface, IpSecManager.DIRECTION_OUT, outTransform);
+
+ mTestRunnableFactory
+ .getIpSecTunnelTestRunnable(
+ transportInTunnelMode,
+ spi,
+ localInner,
+ remoteInner,
+ localOuter,
+ remoteOuter,
+ inTransportTransform,
+ outTransportTransform,
+ useEncap ? encapSocket.getPort() : 0,
+ 0,
+ expectedPacketSize)
+ .run(ipsecNetwork, tunnelIface, tunUtils);
+ }
+ }
+ }
+ }
+
private void checkTunnelOutput(
int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
throws Exception {
@@ -426,6 +650,28 @@
new InputPacketGeneratorIpSecTunnelTestRunnableFactory());
}
+ private void checkMigrateTunnelOutput(
+ int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
+ throws Exception {
+ checkTunnel(
+ innerFamily,
+ outerFamily,
+ useEncap,
+ transportInTunnelMode,
+ new MigrateIpSecTunnelTestRunnableFactory(true));
+ }
+
+ private void checkMigrateTunnelInput(
+ int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
+ throws Exception {
+ checkTunnel(
+ innerFamily,
+ outerFamily,
+ useEncap,
+ transportInTunnelMode,
+ new MigrateIpSecTunnelTestRunnableFactory(false));
+ }
+
/**
* Validates that the kernel can talk to itself.
*
@@ -579,7 +825,8 @@
IpSecManager.SecurityParameterIndex outSpi =
mISM.allocateSecurityParameterIndex(remoteOuter, spi);
IpSecManager.IpSecTunnelInterface tunnelIface =
- mISM.createIpSecTunnelInterface(localOuter, remoteOuter, sTunNetwork)) {
+ mISM.createIpSecTunnelInterface(
+ localOuter, remoteOuter, sTunWrapper.network)) {
// Build the test network
tunnelIface.addAddress(localInner, innerPrefixLen);
testNetworkCb = mCtsNetUtils.setupAndGetTestNetwork(tunnelIface.getInterfaceName());
@@ -615,7 +862,7 @@
mISM.applyTunnelModeTransform(
tunnelIface, IpSecManager.DIRECTION_OUT, outTransform);
- innerSocketPort = test.run(testNetwork);
+ innerSocketPort = test.run(testNetwork, tunnelIface, sTunWrapper.utils);
}
// Teardown the test network
@@ -739,6 +986,14 @@
return maybeEncapPacket(srcOuter, dstOuter, encapPort, espPayload).getPacketBytes();
}
+ private void doTestMigrateTunnel(
+ int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
+ throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+ checkTunnelOutput(innerFamily, outerFamily, useEncap, transportInTunnelMode);
+ checkTunnelInput(innerFamily, outerFamily, useEncap, transportInTunnelMode);
+ }
+
// Transport-in-Tunnel mode tests
@Test
public void testTransportInTunnelModeV4InV4() throws Exception {
@@ -747,6 +1002,12 @@
checkTunnelInput(AF_INET, AF_INET, false, true);
}
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
+ public void testMigrateTransportInTunnelModeV4InV4() throws Exception {
+ doTestMigrateTunnel(AF_INET, AF_INET, false, true);
+ }
+
@Test
public void testTransportInTunnelModeV4InV4Reflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -760,6 +1021,12 @@
checkTunnelInput(AF_INET, AF_INET, true, true);
}
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
+ public void testMigrateTransportInTunnelModeV4InV4UdpEncap() throws Exception {
+ doTestMigrateTunnel(AF_INET, AF_INET, true, true);
+ }
+
@Test
public void testTransportInTunnelModeV4InV4UdpEncapReflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -773,6 +1040,12 @@
checkTunnelInput(AF_INET, AF_INET6, false, true);
}
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
+ public void testMigrateTransportInTunnelModeV4InV6() throws Exception {
+ doTestMigrateTunnel(AF_INET, AF_INET6, false, true);
+ }
+
@Test
public void testTransportInTunnelModeV4InV6Reflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -786,6 +1059,12 @@
checkTunnelInput(AF_INET6, AF_INET, false, true);
}
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
+ public void testMigrateTransportInTunnelModeV6InV4() throws Exception {
+ doTestMigrateTunnel(AF_INET6, AF_INET, false, true);
+ }
+
@Test
public void testTransportInTunnelModeV6InV4Reflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -799,6 +1078,12 @@
checkTunnelInput(AF_INET6, AF_INET, true, true);
}
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
+ public void testMigrateTransportInTunnelModeV6InV4UdpEncap() throws Exception {
+ doTestMigrateTunnel(AF_INET6, AF_INET, true, true);
+ }
+
@Test
public void testTransportInTunnelModeV6InV4UdpEncapReflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -812,6 +1097,12 @@
checkTunnelInput(AF_INET, AF_INET6, false, true);
}
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
+ public void testMigrateTransportInTunnelModeV6InV6() throws Exception {
+ doTestMigrateTunnel(AF_INET, AF_INET6, false, true);
+ }
+
@Test
public void testTransportInTunnelModeV6InV6Reflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -826,6 +1117,12 @@
checkTunnelInput(AF_INET, AF_INET, false, false);
}
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
+ public void testMigrateTunnelV4InV4() throws Exception {
+ doTestMigrateTunnel(AF_INET, AF_INET, false, false);
+ }
+
@Test
public void testTunnelV4InV4Reflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -839,6 +1136,12 @@
checkTunnelInput(AF_INET, AF_INET, true, false);
}
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
+ public void testMigrateTunnelV4InV4UdpEncap() throws Exception {
+ doTestMigrateTunnel(AF_INET, AF_INET, true, false);
+ }
+
@Test
public void testTunnelV4InV4UdpEncapReflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -852,6 +1155,12 @@
checkTunnelInput(AF_INET, AF_INET6, false, false);
}
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
+ public void testMigrateTunnelV4InV6() throws Exception {
+ doTestMigrateTunnel(AF_INET, AF_INET6, false, false);
+ }
+
@Test
public void testTunnelV4InV6Reflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -865,6 +1174,12 @@
checkTunnelInput(AF_INET6, AF_INET, false, false);
}
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
+ public void testMigrateTunnelV6InV4() throws Exception {
+ doTestMigrateTunnel(AF_INET6, AF_INET, false, false);
+ }
+
@Test
public void testTunnelV6InV4Reflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -878,6 +1193,12 @@
checkTunnelInput(AF_INET6, AF_INET, true, false);
}
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
+ public void testMigrateTunnelV6InV4UdpEncap() throws Exception {
+ doTestMigrateTunnel(AF_INET6, AF_INET, true, false);
+ }
+
@Test
public void testTunnelV6InV4UdpEncapReflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -891,6 +1212,12 @@
checkTunnelInput(AF_INET6, AF_INET6, false, false);
}
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
+ public void testMigrateTunnelV6InV6() throws Exception {
+ doTestMigrateTunnel(AF_INET6, AF_INET6, false, false);
+ }
+
@Test
public void testTunnelV6InV6Reflected() throws Exception {
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index c505cef..ccc9416 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -29,9 +29,9 @@
import android.net.NattKeepalivePacketData
import android.net.Network
import android.net.NetworkAgent
+import android.net.NetworkAgentConfig
import android.net.NetworkAgent.INVALID_NETWORK
import android.net.NetworkAgent.VALID_NETWORK
-import android.net.NetworkAgentConfig
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED
@@ -46,9 +46,17 @@
import android.net.NetworkCapabilities.TRANSPORT_VPN
import android.net.NetworkInfo
import android.net.NetworkProvider
+import android.net.NetworkReleasedException
import android.net.NetworkRequest
import android.net.NetworkScore
import android.net.RouteInfo
+import android.net.QosCallback
+import android.net.QosCallbackException
+import android.net.QosCallback.QosCallbackRegistrationException
+import android.net.QosFilter
+import android.net.QosSession
+import android.net.QosSessionAttributes
+import android.net.QosSocketInfo
import android.net.SocketKeepalive
import android.net.Uri
import android.net.VpnManager
@@ -59,12 +67,17 @@
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnNetworkCreated
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnNetworkDestroyed
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnNetworkUnwanted
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnRegisterQosCallback
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnRemoveKeepalivePacketFilter
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnSaveAcceptUnvalidated
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnSignalStrengthThresholdsUpdated
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnStartSocketKeepalive
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnStopSocketKeepalive
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnUnregisterQosCallback
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnValidationStatus
+import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnError
+import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnQosSessionAvailable
+import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnQosSessionLost
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
@@ -72,6 +85,7 @@
import android.os.Message
import android.os.SystemClock
import android.telephony.TelephonyManager
+import android.telephony.data.EpsBearerQosSessionAttributes
import android.util.DebugUtils.valueToString
import androidx.test.InstrumentationRegistry
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
@@ -97,9 +111,13 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.timeout
import org.mockito.Mockito.verify
+import java.net.InetAddress
+import java.net.InetSocketAddress
+import java.net.Socket
import java.time.Duration
import java.util.Arrays
import java.util.UUID
+import java.util.concurrent.Executors
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
@@ -143,7 +161,7 @@
private val LOCAL_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1")
private val REMOTE_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.2")
- private val mCM = realContext.getSystemService(ConnectivityManager::class.java)
+ private val mCM = realContext.getSystemService(ConnectivityManager::class.java)!!
private val mHandlerThread = HandlerThread("${javaClass.simpleName} handler thread")
private val mFakeConnectivityService = FakeConnectivityService()
@@ -152,6 +170,7 @@
private val agentsToCleanUp = mutableListOf<NetworkAgent>()
private val callbacksToCleanUp = mutableListOf<TestableNetworkCallback>()
+ private var qosTestSocket: Socket? = null
@Before
fun setUp() {
@@ -163,6 +182,7 @@
fun tearDown() {
agentsToCleanUp.forEach { it.unregister() }
callbacksToCleanUp.forEach { mCM.unregisterNetworkCallback(it) }
+ qosTestSocket?.close()
mHandlerThread.quitSafely()
instrumentation.getUiAutomation().dropShellPermissionIdentity()
}
@@ -228,6 +248,11 @@
data class OnSignalStrengthThresholdsUpdated(val thresholds: IntArray) : CallbackEntry()
object OnNetworkCreated : CallbackEntry()
object OnNetworkDestroyed : CallbackEntry()
+ data class OnRegisterQosCallback(
+ val callbackId: Int,
+ val filter: QosFilter
+ ) : CallbackEntry()
+ data class OnUnregisterQosCallback(val callbackId: Int) : CallbackEntry()
}
override fun onBandwidthUpdateRequested() {
@@ -276,6 +301,14 @@
}
}
+ override fun onQosCallbackRegistered(qosCallbackId: Int, filter: QosFilter) {
+ history.add(OnRegisterQosCallback(qosCallbackId, filter))
+ }
+
+ override fun onQosCallbackUnregistered(qosCallbackId: Int) {
+ history.add(OnUnregisterQosCallback(qosCallbackId))
+ }
+
override fun onValidationStatus(status: Int, uri: Uri?) {
history.add(OnValidationStatus(status, uri))
}
@@ -307,6 +340,12 @@
return foundCallback
}
+ inline fun <reified T : CallbackEntry> expectCallback(valid: (T) -> Boolean) {
+ val foundCallback = history.poll(DEFAULT_TIMEOUT_MS)
+ assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback")
+ assertTrue(valid(foundCallback), "Unexpected callback : $foundCallback")
+ }
+
inline fun <reified T : CallbackEntry> eventuallyExpect() =
history.poll(DEFAULT_TIMEOUT_MS) { it is T }.also {
assertNotNull(it, "Callback ${T::class} not received")
@@ -390,7 +429,7 @@
initialConfig: NetworkAgentConfig? = null,
expectedInitSignalStrengthThresholds: IntArray? = intArrayOf()
): Pair<TestableNetworkAgent, TestableNetworkCallback> {
- val callback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS)
+ val callback = TestableNetworkCallback()
// Ensure this NetworkAgent is never unneeded by filing a request with its specifier.
requestNetwork(makeTestNetworkRequest(specifier = specifier), callback)
val agent = createNetworkAgent(context, specifier, initialConfig = initialConfig)
@@ -651,7 +690,7 @@
assertFalse(vpnNc.hasCapability(NET_CAPABILITY_NOT_VPN))
assertTrue(hasAllTransports(vpnNc, defaultNetworkTransports),
"VPN transports ${Arrays.toString(vpnNc.transportTypes)}" +
- " lacking transports from ${Arrays.toString(defaultNetworkTransports)}")
+ " lacking transports from ${Arrays.toString(defaultNetworkTransports)}")
// Check that when no underlying networks are announced the underlying transport disappears.
agent.setUnderlyingNetworks(listOf<Network>())
@@ -934,4 +973,251 @@
// tearDown() will unregister the requests and agents
}
+
+ private class TestableQosCallback : QosCallback() {
+ val history = ArrayTrackRecord<CallbackEntry>().newReadHead()
+
+ sealed class CallbackEntry {
+ data class OnQosSessionAvailable(val sess: QosSession, val attr: QosSessionAttributes)
+ : CallbackEntry()
+ data class OnQosSessionLost(val sess: QosSession)
+ : CallbackEntry()
+ data class OnError(val ex: QosCallbackException)
+ : CallbackEntry()
+ }
+
+ override fun onQosSessionAvailable(sess: QosSession, attr: QosSessionAttributes) {
+ history.add(OnQosSessionAvailable(sess, attr))
+ }
+
+ override fun onQosSessionLost(sess: QosSession) {
+ history.add(OnQosSessionLost(sess))
+ }
+
+ override fun onError(ex: QosCallbackException) {
+ history.add(OnError(ex))
+ }
+
+ inline fun <reified T : CallbackEntry> expectCallback(): T {
+ val foundCallback = history.poll(DEFAULT_TIMEOUT_MS)
+ assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback")
+ return foundCallback
+ }
+
+ inline fun <reified T : CallbackEntry> expectCallback(valid: (T) -> Boolean) {
+ val foundCallback = history.poll(DEFAULT_TIMEOUT_MS)
+ assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback")
+ assertTrue(valid(foundCallback), "Unexpected callback : $foundCallback")
+ }
+
+ fun assertNoCallback() {
+ assertNull(history.poll(NO_CALLBACK_TIMEOUT),
+ "Callback received")
+ }
+ }
+
+ private fun setupForQosCallbackTesting(): Pair<TestableNetworkAgent, Socket> {
+ val request = NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(TRANSPORT_TEST)
+ .build()
+
+ val callback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS)
+ requestNetwork(request, callback)
+ val (agent, _) = createConnectedNetworkAgent()
+
+ qosTestSocket = assertNotNull(agent.network?.socketFactory?.createSocket()).also {
+ it.bind(InetSocketAddress(InetAddress.getLoopbackAddress(), 0))
+ }
+ return Pair(agent, qosTestSocket!!)
+ }
+
+ @Test
+ fun testQosCallbackRegisterWithUnregister() {
+ val (agent, socket) = setupForQosCallbackTesting()
+
+ val qosCallback = TestableQosCallback()
+ var callbackId = -1
+ Executors.newSingleThreadExecutor().let { executor ->
+ try {
+ val info = QosSocketInfo(agent.network!!, socket)
+ mCM.registerQosCallback(info, executor, qosCallback)
+ callbackId = agent.expectCallback<OnRegisterQosCallback>().callbackId
+
+ assertFailsWith<QosCallbackRegistrationException>(
+ "The same callback cannot be " +
+ "registered more than once without first being unregistered") {
+ mCM.registerQosCallback(info, executor, qosCallback)
+ }
+ } finally {
+ socket.close()
+ mCM.unregisterQosCallback(qosCallback)
+ agent.expectCallback<OnUnregisterQosCallback> { it.callbackId == callbackId }
+ executor.shutdown()
+ }
+ }
+ }
+
+ @Test
+ fun testQosCallbackOnQosSession() {
+ val (agent, socket) = setupForQosCallbackTesting()
+ val qosCallback = TestableQosCallback()
+ Executors.newSingleThreadExecutor().let { executor ->
+ try {
+ val info = QosSocketInfo(agent.network!!, socket)
+ mCM.registerQosCallback(info, executor, qosCallback)
+ val callbackId = agent.expectCallback<OnRegisterQosCallback>().callbackId
+
+ val uniqueSessionId = 4294967397
+ val sessId = 101
+
+ val attributes = createEpsAttributes(5)
+ assertEquals(attributes.qosIdentifier, 5)
+ agent.sendQosSessionAvailable(callbackId, sessId, attributes)
+ qosCallback.expectCallback<OnQosSessionAvailable> {
+ it.sess.sessionId == sessId && it.sess.uniqueId == uniqueSessionId &&
+ it.sess.sessionType == QosSession.TYPE_EPS_BEARER
+ }
+
+ agent.sendQosSessionLost(callbackId, sessId, QosSession.TYPE_EPS_BEARER)
+ qosCallback.expectCallback<OnQosSessionLost> {
+ it.sess.sessionId == sessId && it.sess.uniqueId == uniqueSessionId &&
+ it.sess.sessionType == QosSession.TYPE_EPS_BEARER
+ }
+
+ // Make sure that we don't get more qos callbacks
+ mCM.unregisterQosCallback(qosCallback)
+ agent.expectCallback<OnUnregisterQosCallback>()
+
+ agent.sendQosSessionLost(callbackId, sessId, QosSession.TYPE_EPS_BEARER)
+ qosCallback.assertNoCallback()
+ } finally {
+ socket.close()
+
+ // safety precaution
+ mCM.unregisterQosCallback(qosCallback)
+
+ executor.shutdown()
+ }
+ }
+ }
+
+ @Test
+ fun testQosCallbackOnError() {
+ val (agent, socket) = setupForQosCallbackTesting()
+ val qosCallback = TestableQosCallback()
+ Executors.newSingleThreadExecutor().let { executor ->
+ try {
+ val info = QosSocketInfo(agent.network!!, socket)
+ mCM.registerQosCallback(info, executor, qosCallback)
+ val callbackId = agent.expectCallback<OnRegisterQosCallback>().callbackId
+
+ val sessId = 101
+ val attributes = createEpsAttributes()
+
+ // Double check that this is wired up and ready to go
+ agent.sendQosSessionAvailable(callbackId, sessId, attributes)
+ qosCallback.expectCallback<OnQosSessionAvailable>()
+
+ // Check that onError is coming through correctly
+ agent.sendQosCallbackError(callbackId,
+ QosCallbackException.EX_TYPE_FILTER_NOT_SUPPORTED)
+ qosCallback.expectCallback<OnError> {
+ it.ex.cause is UnsupportedOperationException
+ }
+
+ // Ensure that when an error occurs the callback was also unregistered
+ agent.sendQosSessionLost(callbackId, sessId, QosSession.TYPE_EPS_BEARER)
+ qosCallback.assertNoCallback()
+ } finally {
+ socket.close()
+
+ // Make sure that the callback is fully unregistered
+ mCM.unregisterQosCallback(qosCallback)
+
+ executor.shutdown()
+ }
+ }
+ }
+
+ @Test
+ fun testQosCallbackIdsAreMappedCorrectly() {
+ val (agent, socket) = setupForQosCallbackTesting()
+ val qosCallback1 = TestableQosCallback()
+ val qosCallback2 = TestableQosCallback()
+ Executors.newSingleThreadExecutor().let { executor ->
+ try {
+ val info = QosSocketInfo(agent.network!!, socket)
+ mCM.registerQosCallback(info, executor, qosCallback1)
+ val callbackId1 = agent.expectCallback<OnRegisterQosCallback>().callbackId
+
+ mCM.registerQosCallback(info, executor, qosCallback2)
+ val callbackId2 = agent.expectCallback<OnRegisterQosCallback>().callbackId
+
+ val sessId1 = 101
+ val attributes1 = createEpsAttributes(1)
+
+ // Check #1
+ agent.sendQosSessionAvailable(callbackId1, sessId1, attributes1)
+ qosCallback1.expectCallback<OnQosSessionAvailable>()
+ qosCallback2.assertNoCallback()
+
+ // Check #2
+ val sessId2 = 102
+ val attributes2 = createEpsAttributes(2)
+ agent.sendQosSessionAvailable(callbackId2, sessId2, attributes2)
+ qosCallback1.assertNoCallback()
+ qosCallback2.expectCallback<OnQosSessionAvailable> { sessId2 == it.sess.sessionId }
+ } finally {
+ socket.close()
+
+ // Make sure that the callback is fully unregistered
+ mCM.unregisterQosCallback(qosCallback1)
+ mCM.unregisterQosCallback(qosCallback2)
+
+ executor.shutdown()
+ }
+ }
+ }
+
+ @Test
+ fun testQosCallbackWhenNetworkReleased() {
+ val (agent, socket) = setupForQosCallbackTesting()
+ Executors.newSingleThreadExecutor().let { executor ->
+ try {
+ val qosCallback1 = TestableQosCallback()
+ val qosCallback2 = TestableQosCallback()
+ try {
+ val info = QosSocketInfo(agent.network!!, socket)
+ mCM.registerQosCallback(info, executor, qosCallback1)
+ mCM.registerQosCallback(info, executor, qosCallback2)
+ agent.unregister()
+
+ qosCallback1.expectCallback<OnError> {
+ it.ex.cause is NetworkReleasedException
+ }
+
+ qosCallback2.expectCallback<OnError> {
+ it.ex.cause is NetworkReleasedException
+ }
+ } finally {
+ socket.close()
+ mCM.unregisterQosCallback(qosCallback1)
+ mCM.unregisterQosCallback(qosCallback2)
+ }
+ } finally {
+ socket.close()
+ executor.shutdown()
+ }
+ }
+ }
+
+ private fun createEpsAttributes(qci: Int = 1): EpsBearerQosSessionAttributes {
+ val remoteAddresses = ArrayList<InetSocketAddress>()
+ remoteAddresses.add(InetSocketAddress("2001:db8::123", 80))
+ return EpsBearerQosSessionAttributes(
+ qci, 2, 3, 4, 5,
+ remoteAddresses
+ )
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt b/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt
new file mode 100644
index 0000000..8f17199
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt
@@ -0,0 +1,217 @@
+/*
+ * 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 android.Manifest.permission.MANAGE_TEST_NETWORKS
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.LinkProperties
+import android.net.NetworkAgent
+import android.net.NetworkAgentConfig
+import android.net.NetworkCapabilities
+import android.net.NetworkProvider
+import android.net.NetworkRequest
+import android.net.NetworkScore
+import android.net.VpnManager
+import android.net.VpnTransportInfo
+import android.os.Build
+import android.os.Handler
+import android.os.HandlerThread
+import androidx.test.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.TestableNetworkCallback.HasNetwork
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+// This test doesn't really have a constraint on how fast the methods should return. If it's
+// going to fail, it will simply wait forever, so setting a high timeout lowers the flake ratio
+// without affecting the run time of successful runs. Thus, set a very high timeout.
+private const val TIMEOUT_MS = 30_000L
+// When waiting for a NetworkCallback to determine there was no timeout, waiting is the
+// only possible thing (the relevant handler is the one in the real ConnectivityService,
+// and then there is the Binder call), so have a short timeout for this as it will be
+// exhausted every time.
+private const val NO_CALLBACK_TIMEOUT = 200L
+
+private val testContext: Context
+ get() = InstrumentationRegistry.getContext()
+
+private fun score(exiting: Boolean = false, primary: Boolean = false) =
+ NetworkScore.Builder().setExiting(exiting).setTransportPrimary(primary)
+ // TODO : have a constant KEEP_CONNECTED_FOR_TEST ?
+ .setKeepConnectedReason(NetworkScore.KEEP_CONNECTED_FOR_HANDOVER)
+ .build()
+
+@IgnoreUpTo(Build.VERSION_CODES.R)
+@RunWith(DevSdkIgnoreRunner::class)
+class NetworkScoreTest {
+ private val mCm = testContext.getSystemService(ConnectivityManager::class.java)
+ private val mHandlerThread = HandlerThread("${javaClass.simpleName} handler thread")
+ private val mHandler by lazy { Handler(mHandlerThread.looper) }
+ private val agentsToCleanUp = mutableListOf<NetworkAgent>()
+ private val callbacksToCleanUp = mutableListOf<TestableNetworkCallback>()
+
+ @Before
+ fun setUp() {
+ mHandlerThread.start()
+ }
+
+ @After
+ fun tearDown() {
+ agentsToCleanUp.forEach { it.unregister() }
+ mHandlerThread.quitSafely()
+ callbacksToCleanUp.forEach { mCm.unregisterNetworkCallback(it) }
+ }
+
+ // Returns a networkCallback that sends onAvailable on the best network with TRANSPORT_TEST.
+ private fun makeTestNetworkCallback() = TestableNetworkCallback(TIMEOUT_MS).also { cb ->
+ mCm.registerBestMatchingNetworkCallback(NetworkRequest.Builder().clearCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_TEST).build(), cb, mHandler)
+ callbacksToCleanUp.add(cb)
+ }
+
+ // TestNetworkCallback is made to interact with a wrapper of NetworkAgent, because it's
+ // made for ConnectivityServiceTest.
+ // TODO : have TestNetworkCallback work for NetworkAgent too and remove this class.
+ private class AgentWrapper(val agent: NetworkAgent) : HasNetwork {
+ override val network = agent.network
+ fun sendNetworkScore(s: NetworkScore) = agent.sendNetworkScore(s)
+ }
+
+ private fun createTestNetworkAgent(
+ // The network always has TRANSPORT_TEST, plus optional transports
+ optionalTransports: IntArray = IntArray(size = 0),
+ everUserSelected: Boolean = false,
+ acceptUnvalidated: Boolean = false,
+ isExiting: Boolean = false,
+ isPrimary: Boolean = false
+ ): AgentWrapper {
+ val nc = NetworkCapabilities.Builder().apply {
+ addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ optionalTransports.forEach { addTransportType(it) }
+ // Add capabilities that are common, just for realism. It's not strictly necessary
+ addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+ addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
+ // Remove capabilities that a test network agent shouldn't have and that are not
+ // needed for the purposes of this test.
+ removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+ removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ if (optionalTransports.contains(NetworkCapabilities.TRANSPORT_VPN)) {
+ addTransportType(NetworkCapabilities.TRANSPORT_VPN)
+ removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+ setTransportInfo(VpnTransportInfo(VpnManager.TYPE_VPN_SERVICE, null))
+ }
+ addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+ }.build()
+ val config = NetworkAgentConfig.Builder()
+ .setExplicitlySelected(everUserSelected)
+ .setUnvalidatedConnectivityAcceptable(acceptUnvalidated)
+ .build()
+ val score = score(exiting = isExiting, primary = isPrimary)
+ val context = testContext
+ val looper = mHandlerThread.looper
+ val agent = object : NetworkAgent(context, looper, "NetworkScore test agent", nc,
+ LinkProperties(), score, config, NetworkProvider(context, looper,
+ "NetworkScore test provider")) {}.also {
+ agentsToCleanUp.add(it)
+ }
+ runWithShellPermissionIdentity({ agent.register() }, MANAGE_TEST_NETWORKS)
+ agent.markConnected()
+ return AgentWrapper(agent)
+ }
+
+ @Test
+ fun testExitingLosesAndOldSatisfierWins() {
+ val cb = makeTestNetworkCallback()
+ val agent1 = createTestNetworkAgent()
+ cb.expectAvailableThenValidatedCallbacks(agent1)
+ val agent2 = createTestNetworkAgent()
+ // Because the existing network must win, the callback stays on agent1.
+ cb.assertNoCallback(NO_CALLBACK_TIMEOUT)
+ agent1.sendNetworkScore(score(exiting = true))
+ // Now that agent1 is exiting, the callback is satisfied by agent2.
+ cb.expectAvailableCallbacks(agent2.network)
+ agent1.sendNetworkScore(score(exiting = false))
+ // Agent1 is no longer exiting, but agent2 is the current satisfier.
+ cb.assertNoCallback(NO_CALLBACK_TIMEOUT)
+ }
+
+ @Test
+ fun testVpnWins() {
+ val cb = makeTestNetworkCallback()
+ val agent1 = createTestNetworkAgent()
+ cb.expectAvailableThenValidatedCallbacks(agent1.network)
+ val agent2 = createTestNetworkAgent(intArrayOf(NetworkCapabilities.TRANSPORT_VPN))
+ // VPN wins out against agent1 even before it's validated (hence the "then validated",
+ // because it becomes the best network for this callback before it validates)
+ cb.expectAvailableThenValidatedCallbacks(agent2.network)
+ }
+
+ @Test
+ fun testEverUserSelectedAcceptUnvalidatedWins() {
+ val cb = makeTestNetworkCallback()
+ val agent1 = createTestNetworkAgent()
+ cb.expectAvailableThenValidatedCallbacks(agent1.network)
+ val agent2 = createTestNetworkAgent(everUserSelected = true, acceptUnvalidated = true)
+ // agent2 wins out against agent1 even before it's validated, because user-selected and
+ // accept unvalidated networks should win against even networks that are validated.
+ cb.expectAvailableThenValidatedCallbacks(agent2.network)
+ }
+
+ @Test
+ fun testPreferredTransportOrder() {
+ val cb = makeTestNetworkCallback()
+ val agentCell = createTestNetworkAgent(intArrayOf(NetworkCapabilities.TRANSPORT_CELLULAR))
+ cb.expectAvailableThenValidatedCallbacks(agentCell.network)
+ val agentWifi = createTestNetworkAgent(intArrayOf(NetworkCapabilities.TRANSPORT_WIFI))
+ // In the absence of other discriminating factors, agentWifi wins against agentCell because
+ // of its better transport, but only after it validates.
+ cb.expectAvailableDoubleValidatedCallbacks(agentWifi)
+ val agentEth = createTestNetworkAgent(intArrayOf(NetworkCapabilities.TRANSPORT_ETHERNET))
+ // Likewise, agentEth wins against agentWifi after validation because of its better
+ // transport.
+ cb.expectAvailableCallbacksValidated(agentEth)
+ }
+
+ @Test
+ fun testTransportPrimary() {
+ val cb = makeTestNetworkCallback()
+ val agent1 = createTestNetworkAgent()
+ cb.expectAvailableThenValidatedCallbacks(agent1)
+ val agent2 = createTestNetworkAgent()
+ // Because the existing network must win, the callback stays on agent1.
+ cb.assertNoCallback(NO_CALLBACK_TIMEOUT)
+ agent2.sendNetworkScore(score(primary = true))
+ // Now that agent2 is primary, the callback is satisfied by agent2.
+ cb.expectAvailableCallbacks(agent2.network)
+ agent1.sendNetworkScore(score(primary = true))
+ // Agent1 is primary too, but agent2 is the current satisfier
+ cb.assertNoCallback(NO_CALLBACK_TIMEOUT)
+ agent2.sendNetworkScore(score(primary = false))
+ // Now agent1 is primary and agent2 isn't
+ cb.expectAvailableCallbacks(agent1.network)
+ }
+
+ // TODO (b/187929636) : add a test making sure that validated networks win over unvalidated
+ // ones. Right now this is not possible because this CTS can't directly manipulate the
+ // validation state of a network.
+}
diff --git a/tests/cts/net/src/android/net/ipv6/cts/PingTest.java b/tests/cts/net/src/android/net/ipv6/cts/PingTest.java
index 146fd83..8665fc8 100644
--- a/tests/cts/net/src/android/net/ipv6/cts/PingTest.java
+++ b/tests/cts/net/src/android/net/ipv6/cts/PingTest.java
@@ -115,7 +115,7 @@
// Receive the response.
if (useRecvfrom) {
- InetSocketAddress from = new InetSocketAddress();
+ InetSocketAddress from = new InetSocketAddress(0);
bytesRead = Os.recvfrom(s, responseBuffer, 0, from);
// Check the source address and scope ID.
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
index c220326..8c5372d 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
@@ -440,12 +440,6 @@
return callback.getTetheringInterfaceRegexps().getTetherableWifiRegexs();
}
- public static boolean isWifiTetheringSupported(final Context ctx,
- final TestTetheringEventCallback callback) throws Exception {
- return !getWifiTetherableInterfaceRegexps(callback).isEmpty()
- && isPortableHotspotSupported(ctx);
- }
-
/* Returns if wifi supports hotspot. */
private static boolean isPortableHotspotSupported(final Context ctx) throws Exception {
final PackageManager pm = ctx.getPackageManager();
@@ -522,4 +516,8 @@
callback.expectNoTetheringActive();
callback.expectOneOfOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
}
+
+ public void stopAllTethering() {
+ mTm.stopAllTethering();
+ }
}
diff --git a/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java b/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
index 6bd2bd5..be95b6c 100644
--- a/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
+++ b/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
@@ -36,9 +36,11 @@
import android.net.NetworkStats.Entry;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
+import android.os.Build;
import android.os.RemoteException;
import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -51,6 +53,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class NetworkStatsManagerTest {
private static final String TEST_SUBSCRIBER_ID = "subid";
diff --git a/tests/unit/java/android/net/ConnectivityManagerTest.java b/tests/unit/java/android/net/ConnectivityManagerTest.java
index 07f22a2..b36e379 100644
--- a/tests/unit/java/android/net/ConnectivityManagerTest.java
+++ b/tests/unit/java/android/net/ConnectivityManagerTest.java
@@ -59,6 +59,7 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.net.ConnectivityManager.NetworkCallback;
+import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Handler;
@@ -67,6 +68,7 @@
import android.os.Messenger;
import android.os.Process;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -79,6 +81,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class ConnectivityManagerTest {
@Mock Context mCtx;
@@ -320,26 +323,34 @@
NetworkCallback nullCallback = null;
PendingIntent nullIntent = null;
- mustFail(() -> { manager.requestNetwork(null, callback); });
- mustFail(() -> { manager.requestNetwork(request, nullCallback); });
- mustFail(() -> { manager.requestNetwork(request, callback, null); });
- mustFail(() -> { manager.requestNetwork(request, callback, -1); });
- mustFail(() -> { manager.requestNetwork(request, nullIntent); });
+ mustFail(() -> manager.requestNetwork(null, callback));
+ mustFail(() -> manager.requestNetwork(request, nullCallback));
+ mustFail(() -> manager.requestNetwork(request, callback, null));
+ mustFail(() -> manager.requestNetwork(request, callback, -1));
+ mustFail(() -> manager.requestNetwork(request, nullIntent));
- mustFail(() -> { manager.registerNetworkCallback(null, callback, handler); });
- mustFail(() -> { manager.registerNetworkCallback(request, null, handler); });
- mustFail(() -> { manager.registerNetworkCallback(request, callback, null); });
- mustFail(() -> { manager.registerNetworkCallback(request, nullIntent); });
+ mustFail(() -> manager.requestBackgroundNetwork(null, callback, handler));
+ mustFail(() -> manager.requestBackgroundNetwork(request, null, handler));
+ mustFail(() -> manager.requestBackgroundNetwork(request, callback, null));
- mustFail(() -> { manager.registerDefaultNetworkCallback(null, handler); });
- mustFail(() -> { manager.registerDefaultNetworkCallback(callback, null); });
+ mustFail(() -> manager.registerNetworkCallback(null, callback, handler));
+ mustFail(() -> manager.registerNetworkCallback(request, null, handler));
+ mustFail(() -> manager.registerNetworkCallback(request, callback, null));
+ mustFail(() -> manager.registerNetworkCallback(request, nullIntent));
- mustFail(() -> { manager.registerSystemDefaultNetworkCallback(null, handler); });
- mustFail(() -> { manager.registerSystemDefaultNetworkCallback(callback, null); });
+ mustFail(() -> manager.registerDefaultNetworkCallback(null, handler));
+ mustFail(() -> manager.registerDefaultNetworkCallback(callback, null));
- mustFail(() -> { manager.unregisterNetworkCallback(nullCallback); });
- mustFail(() -> { manager.unregisterNetworkCallback(nullIntent); });
- mustFail(() -> { manager.releaseNetworkRequest(nullIntent); });
+ mustFail(() -> manager.registerSystemDefaultNetworkCallback(null, handler));
+ mustFail(() -> manager.registerSystemDefaultNetworkCallback(callback, null));
+
+ mustFail(() -> manager.registerBestMatchingNetworkCallback(null, callback, handler));
+ mustFail(() -> manager.registerBestMatchingNetworkCallback(request, null, handler));
+ mustFail(() -> manager.registerBestMatchingNetworkCallback(request, callback, null));
+
+ mustFail(() -> manager.unregisterNetworkCallback(nullCallback));
+ mustFail(() -> manager.unregisterNetworkCallback(nullIntent));
+ mustFail(() -> manager.releaseNetworkRequest(nullIntent));
}
static void mustFail(Runnable fn) {
diff --git a/tests/unit/java/android/net/Ikev2VpnProfileTest.java b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
index 0707ef3..afd85e8 100644
--- a/tests/unit/java/android/net/Ikev2VpnProfileTest.java
+++ b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
@@ -23,8 +23,10 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.os.Build;
import android.test.mock.MockContext;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -52,6 +54,7 @@
/** Unit tests for {@link Ikev2VpnProfile.Builder}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class Ikev2VpnProfileTest {
private static final String SERVER_ADDR_STRING = "1.2.3.4";
private static final String IDENTITY_STRING = "Identity";
diff --git a/tests/unit/java/android/net/IpMemoryStoreTest.java b/tests/unit/java/android/net/IpMemoryStoreTest.java
index 0b13800..6be5396 100644
--- a/tests/unit/java/android/net/IpMemoryStoreTest.java
+++ b/tests/unit/java/android/net/IpMemoryStoreTest.java
@@ -36,8 +36,10 @@
import android.net.ipmemorystore.NetworkAttributesParcelable;
import android.net.ipmemorystore.Status;
import android.net.networkstack.ModuleNetworkStackClient;
+import android.os.Build;
import android.os.RemoteException;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -55,6 +57,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class IpMemoryStoreTest {
private static final String TAG = IpMemoryStoreTest.class.getSimpleName();
private static final String TEST_CLIENT_ID = "testClientId";
diff --git a/tests/unit/java/android/net/IpSecAlgorithmTest.java b/tests/unit/java/android/net/IpSecAlgorithmTest.java
index 3a8d600..fc08408 100644
--- a/tests/unit/java/android/net/IpSecAlgorithmTest.java
+++ b/tests/unit/java/android/net/IpSecAlgorithmTest.java
@@ -28,6 +28,7 @@
import android.os.Build;
import android.os.Parcel;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -46,6 +47,7 @@
/** Unit tests for {@link IpSecAlgorithm}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class IpSecAlgorithmTest {
private static final byte[] KEY_MATERIAL;
diff --git a/tests/unit/java/android/net/IpSecConfigTest.java b/tests/unit/java/android/net/IpSecConfigTest.java
index 25e225e..457c923 100644
--- a/tests/unit/java/android/net/IpSecConfigTest.java
+++ b/tests/unit/java/android/net/IpSecConfigTest.java
@@ -23,6 +23,9 @@
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
+import android.os.Build;
+
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import org.junit.Test;
@@ -32,6 +35,7 @@
/** Unit tests for {@link IpSecConfig}. */
@SmallTest
@RunWith(JUnit4.class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class IpSecConfigTest {
@Test
diff --git a/tests/unit/java/android/net/IpSecManagerTest.java b/tests/unit/java/android/net/IpSecManagerTest.java
index 730e2d5..3fd0064 100644
--- a/tests/unit/java/android/net/IpSecManagerTest.java
+++ b/tests/unit/java/android/net/IpSecManagerTest.java
@@ -31,9 +31,11 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.os.Build;
import android.system.Os;
import android.test.mock.MockContext;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -50,6 +52,7 @@
/** Unit tests for {@link IpSecManager}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class IpSecManagerTest {
private static final int TEST_UDP_ENCAP_PORT = 34567;
diff --git a/tests/unit/java/android/net/IpSecTransformTest.java b/tests/unit/java/android/net/IpSecTransformTest.java
index 424f23d..96b09c3 100644
--- a/tests/unit/java/android/net/IpSecTransformTest.java
+++ b/tests/unit/java/android/net/IpSecTransformTest.java
@@ -19,6 +19,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import android.os.Build;
+
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import org.junit.Test;
@@ -28,6 +31,7 @@
/** Unit tests for {@link IpSecTransform}. */
@SmallTest
@RunWith(JUnit4.class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class IpSecTransformTest {
@Test
diff --git a/tests/unit/java/android/net/KeepalivePacketDataUtilTest.java b/tests/unit/java/android/net/KeepalivePacketDataUtilTest.java
index bc6dbf2..3fcb515 100644
--- a/tests/unit/java/android/net/KeepalivePacketDataUtilTest.java
+++ b/tests/unit/java/android/net/KeepalivePacketDataUtilTest.java
@@ -24,8 +24,11 @@
import static org.junit.Assert.fail;
import android.net.util.KeepalivePacketDataUtil;
+import android.os.Build;
import android.util.Log;
+import androidx.test.filters.SdkSuppress;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -36,6 +39,7 @@
import java.nio.ByteBuffer;
@RunWith(JUnit4.class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public final class KeepalivePacketDataUtilTest {
private static final byte[] IPV4_KEEPALIVE_SRC_ADDR = {10, 0, 0, 1};
private static final byte[] IPV4_KEEPALIVE_DST_ADDR = {10, 0, 0, 5};
diff --git a/tests/unit/java/android/net/MacAddressTest.java b/tests/unit/java/android/net/MacAddressTest.java
index 6de31f6..0039e44 100644
--- a/tests/unit/java/android/net/MacAddressTest.java
+++ b/tests/unit/java/android/net/MacAddressTest.java
@@ -22,6 +22,9 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.os.Build;
+
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -36,6 +39,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class MacAddressTest {
static class AddrTypeTestCase {
diff --git a/tests/unit/java/android/net/NetworkIdentityTest.kt b/tests/unit/java/android/net/NetworkIdentityTest.kt
index eb2b85c..4d04b19 100644
--- a/tests/unit/java/android/net/NetworkIdentityTest.kt
+++ b/tests/unit/java/android/net/NetworkIdentityTest.kt
@@ -20,12 +20,15 @@
import android.net.NetworkIdentity.OEM_PAID
import android.net.NetworkIdentity.OEM_PRIVATE
import android.net.NetworkIdentity.getOemBitfield
+import android.os.Build
+import androidx.test.filters.SdkSuppress
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import kotlin.test.assertEquals
@RunWith(JUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
class NetworkIdentityTest {
@Test
fun testGetOemBitfield() {
diff --git a/tests/unit/java/android/net/NetworkStatsHistoryTest.java b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
index 13558cd..3ecce50 100644
--- a/tests/unit/java/android/net/NetworkStatsHistoryTest.java
+++ b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
@@ -38,9 +38,11 @@
import static org.junit.Assert.assertTrue;
import android.content.Context;
+import android.os.Build;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -58,6 +60,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class NetworkStatsHistoryTest {
private static final String TAG = "NetworkStatsHistoryTest";
diff --git a/tests/unit/java/android/net/NetworkStatsTest.java b/tests/unit/java/android/net/NetworkStatsTest.java
index 23d5a7e..9a3f4c2 100644
--- a/tests/unit/java/android/net/NetworkStatsTest.java
+++ b/tests/unit/java/android/net/NetworkStatsTest.java
@@ -39,9 +39,11 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import android.os.Build;
import android.os.Process;
import android.util.ArrayMap;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -55,6 +57,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class NetworkStatsTest {
private static final String TEST_IFACE = "test0";
diff --git a/tests/unit/java/android/net/NetworkTemplateTest.kt b/tests/unit/java/android/net/NetworkTemplateTest.kt
index cb39a0c..437f961 100644
--- a/tests/unit/java/android/net/NetworkTemplateTest.kt
+++ b/tests/unit/java/android/net/NetworkTemplateTest.kt
@@ -42,7 +42,9 @@
import android.net.NetworkTemplate.buildTemplateWifiWildcard
import android.net.NetworkTemplate.buildTemplateCarrierMetered
import android.net.NetworkTemplate.buildTemplateMobileWithRatType
+import android.os.Build
import android.telephony.TelephonyManager
+import androidx.test.filters.SdkSuppress
import com.android.testutils.assertParcelSane
import org.junit.Before
import org.junit.Test
@@ -61,6 +63,7 @@
private const val TEST_SSID2 = "ssid2"
@RunWith(JUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
class NetworkTemplateTest {
private val mockContext = mock(Context::class.java)
diff --git a/tests/unit/java/android/net/NetworkUtilsTest.java b/tests/unit/java/android/net/NetworkUtilsTest.java
index 7748288..b292998 100644
--- a/tests/unit/java/android/net/NetworkUtilsTest.java
+++ b/tests/unit/java/android/net/NetworkUtilsTest.java
@@ -18,6 +18,9 @@
import static junit.framework.Assert.assertEquals;
+import android.os.Build;
+
+import androidx.test.filters.SdkSuppress;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
@@ -28,6 +31,7 @@
@RunWith(AndroidJUnit4.class)
@androidx.test.filters.SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class NetworkUtilsTest {
@Test
public void testRoutedIPv4AddressCount() {
diff --git a/tests/unit/java/android/net/QosSocketFilterTest.java b/tests/unit/java/android/net/QosSocketFilterTest.java
index 40f8f1b..1635c34 100644
--- a/tests/unit/java/android/net/QosSocketFilterTest.java
+++ b/tests/unit/java/android/net/QosSocketFilterTest.java
@@ -19,6 +19,9 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import android.os.Build;
+
+import androidx.test.filters.SdkSuppress;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
@@ -29,6 +32,7 @@
@RunWith(AndroidJUnit4.class)
@androidx.test.filters.SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class QosSocketFilterTest {
@Test
diff --git a/tests/unit/java/android/net/TelephonyNetworkSpecifierTest.java b/tests/unit/java/android/net/TelephonyNetworkSpecifierTest.java
index 6714bb1..e198c8b 100644
--- a/tests/unit/java/android/net/TelephonyNetworkSpecifierTest.java
+++ b/tests/unit/java/android/net/TelephonyNetworkSpecifierTest.java
@@ -23,8 +23,10 @@
import static org.junit.Assert.assertTrue;
import android.net.wifi.WifiNetworkSpecifier;
+import android.os.Build;
import android.telephony.SubscriptionManager;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import org.junit.Test;
@@ -33,6 +35,7 @@
* Unit test for {@link android.net.TelephonyNetworkSpecifier}.
*/
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class TelephonyNetworkSpecifierTest {
private static final int TEST_SUBID = 5;
private static final String TEST_SSID = "Test123";
diff --git a/tests/unit/java/android/net/VpnManagerTest.java b/tests/unit/java/android/net/VpnManagerTest.java
index 3135062..b4d850a 100644
--- a/tests/unit/java/android/net/VpnManagerTest.java
+++ b/tests/unit/java/android/net/VpnManagerTest.java
@@ -27,9 +27,11 @@
import android.content.ComponentName;
import android.content.Intent;
+import android.os.Build;
import android.test.mock.MockContext;
import android.util.SparseArray;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -43,6 +45,7 @@
/** Unit tests for {@link VpnManager}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class VpnManagerTest {
private static final String PKG_NAME = "fooPackage";
diff --git a/tests/unit/java/android/net/VpnTransportInfoTest.java b/tests/unit/java/android/net/VpnTransportInfoTest.java
index ccaa5cf..ae2ac04 100644
--- a/tests/unit/java/android/net/VpnTransportInfoTest.java
+++ b/tests/unit/java/android/net/VpnTransportInfoTest.java
@@ -24,6 +24,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import android.os.Build;
+
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -32,6 +35,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class VpnTransportInfoTest {
@Test
diff --git a/tests/unit/java/android/net/ipmemorystore/ParcelableTests.java b/tests/unit/java/android/net/ipmemorystore/ParcelableTests.java
index 603c875..219cfff 100644
--- a/tests/unit/java/android/net/ipmemorystore/ParcelableTests.java
+++ b/tests/unit/java/android/net/ipmemorystore/ParcelableTests.java
@@ -21,9 +21,11 @@
import android.net.networkstack.aidl.quirks.IPv6ProvisioningLossQuirk;
import android.net.networkstack.aidl.quirks.IPv6ProvisioningLossQuirkParcelable;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -38,6 +40,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class ParcelableTests {
@Test
public void testNetworkAttributesParceling() throws Exception {
diff --git a/tests/unit/java/android/net/nsd/NsdManagerTest.java b/tests/unit/java/android/net/nsd/NsdManagerTest.java
index 370179c..59a9316 100644
--- a/tests/unit/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/unit/java/android/net/nsd/NsdManagerTest.java
@@ -28,12 +28,14 @@
import static org.mockito.Mockito.verify;
import android.content.Context;
+import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -49,6 +51,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class NsdManagerTest {
static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
diff --git a/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
index 94dfc75..afe54d1 100644
--- a/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
+++ b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
@@ -21,10 +21,12 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.StrictMode;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -38,6 +40,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class NsdServiceInfoTest {
public final static InetAddress LOCALHOST;
diff --git a/tests/unit/java/android/net/util/DnsUtilsTest.java b/tests/unit/java/android/net/util/DnsUtilsTest.java
index b626db8..0bac75e 100644
--- a/tests/unit/java/android/net/util/DnsUtilsTest.java
+++ b/tests/unit/java/android/net/util/DnsUtilsTest.java
@@ -25,7 +25,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.InetAddresses;
+import android.os.Build;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -39,6 +41,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class DnsUtilsTest {
private InetAddress stringToAddress(@NonNull String addr) {
return InetAddresses.parseNumericAddress(addr);
diff --git a/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt b/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
index b62bdbc..65fb4ed 100644
--- a/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
+++ b/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
@@ -25,6 +25,8 @@
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
import android.net.NetworkCapabilities.TRANSPORT_VPN
import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.os.Build
+import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import com.android.internal.R
import org.junit.After
@@ -47,6 +49,7 @@
*/
@RunWith(JUnit4::class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
class KeepaliveUtilsTest {
// Prepare mocked context with given resource strings.
diff --git a/tests/unit/java/android/net/util/MultinetworkPolicyTrackerTest.kt b/tests/unit/java/android/net/util/MultinetworkPolicyTrackerTest.kt
index 78c8fa4..7d602ab 100644
--- a/tests/unit/java/android/net/util/MultinetworkPolicyTrackerTest.kt
+++ b/tests/unit/java/android/net/util/MultinetworkPolicyTrackerTest.kt
@@ -25,11 +25,13 @@
import android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI
import android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE
import android.net.util.MultinetworkPolicyTracker.ActiveDataSubscriptionIdListener
+import android.os.Build
import android.provider.Settings
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import android.test.mock.MockContentResolver
+import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.connectivity.resources.R
@@ -59,6 +61,7 @@
*/
@RunWith(AndroidJUnit4::class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
class MultinetworkPolicyTrackerTest {
private val resources = mock(Resources::class.java).also {
doReturn(R.integer.config_networkAvoidBadWifi).`when`(it).getIdentifier(
diff --git a/tests/unit/java/com/android/internal/net/NetworkUtilsInternalTest.java b/tests/unit/java/com/android/internal/net/NetworkUtilsInternalTest.java
index 3cfecd5..a4d8ea9 100644
--- a/tests/unit/java/com/android/internal/net/NetworkUtilsInternalTest.java
+++ b/tests/unit/java/com/android/internal/net/NetworkUtilsInternalTest.java
@@ -27,9 +27,11 @@
import static org.junit.Assert.fail;
+import android.os.Build;
import android.system.ErrnoException;
import android.system.Os;
+import androidx.test.filters.SdkSuppress;
import androidx.test.runner.AndroidJUnit4;
import libcore.io.IoUtils;
@@ -39,6 +41,7 @@
@RunWith(AndroidJUnit4.class)
@androidx.test.filters.SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class NetworkUtilsInternalTest {
private static void expectSocketSuccess(String msg, int domain, int type) {
diff --git a/tests/unit/java/com/android/internal/net/VpnProfileTest.java b/tests/unit/java/com/android/internal/net/VpnProfileTest.java
index cb0f071..64cbc4e 100644
--- a/tests/unit/java/com/android/internal/net/VpnProfileTest.java
+++ b/tests/unit/java/com/android/internal/net/VpnProfileTest.java
@@ -25,7 +25,9 @@
import static org.junit.Assert.assertTrue;
import android.net.IpSecAlgorithm;
+import android.os.Build;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import org.junit.Test;
@@ -39,6 +41,7 @@
/** Unit tests for {@link VpnProfile}. */
@SmallTest
@RunWith(JUnit4.class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class VpnProfileTest {
private static final String DUMMY_PROFILE_KEY = "Test";
diff --git a/tests/unit/java/com/android/internal/util/BitUtilsTest.java b/tests/unit/java/com/android/internal/util/BitUtilsTest.java
index d2fbdce..9c6ac9b 100644
--- a/tests/unit/java/com/android/internal/util/BitUtilsTest.java
+++ b/tests/unit/java/com/android/internal/util/BitUtilsTest.java
@@ -30,6 +30,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import android.os.Build;
+
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -42,6 +45,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class BitUtilsTest {
@Test
diff --git a/tests/unit/java/com/android/internal/util/RingBufferTest.java b/tests/unit/java/com/android/internal/util/RingBufferTest.java
index d06095a..81a6513 100644
--- a/tests/unit/java/com/android/internal/util/RingBufferTest.java
+++ b/tests/unit/java/com/android/internal/util/RingBufferTest.java
@@ -20,6 +20,9 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
+import android.os.Build;
+
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -28,6 +31,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class RingBufferTest {
@Test
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 3b030d6..0e67583 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -300,6 +300,7 @@
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -390,6 +391,7 @@
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class ConnectivityServiceTest {
private static final String TAG = "ConnectivityServiceTest";
@@ -1496,8 +1498,7 @@
return mService.getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd;
}
- private static class WrappedMultinetworkPolicyTracker extends MultinetworkPolicyTracker {
- volatile boolean mConfigRestrictsAvoidBadWifi;
+ private class WrappedMultinetworkPolicyTracker extends MultinetworkPolicyTracker {
volatile int mConfigMeteredMultipathPreference;
WrappedMultinetworkPolicyTracker(Context c, Handler h, Runnable r) {
@@ -1505,8 +1506,8 @@
}
@Override
- public boolean configRestrictsAvoidBadWifi() {
- return mConfigRestrictsAvoidBadWifi;
+ protected Resources getResourcesForActiveSubId() {
+ return mResources;
}
@Override
@@ -1723,7 +1724,9 @@
.getIdentifier(eq("config_networkSupportedKeepaliveCount"), eq("array"), any());
doReturn(R.array.network_switch_type_name).when(mResources)
.getIdentifier(eq("network_switch_type_name"), eq("array"), any());
-
+ doReturn(R.integer.config_networkAvoidBadWifi).when(mResources)
+ .getIdentifier(eq("config_networkAvoidBadWifi"), eq("integer"), any());
+ doReturn(1).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
final ConnectivityResources connRes = mock(ConnectivityResources.class);
doReturn(mResources).when(connRes).get();
@@ -4645,30 +4648,29 @@
}
@Test
- public void testAvoidBadWifiSetting() throws Exception {
+ public void testSetAllowBadWifiUntil() throws Exception {
+ runAsShell(NETWORK_SETTINGS,
+ () -> mService.setTestAllowBadWifiUntil(System.currentTimeMillis() + 5_000L));
+ waitForIdle();
+ testAvoidBadWifiConfig_controlledBySettings();
+
+ runAsShell(NETWORK_SETTINGS,
+ () -> mService.setTestAllowBadWifiUntil(System.currentTimeMillis() - 5_000L));
+ waitForIdle();
+ testAvoidBadWifiConfig_ignoreSettings();
+ }
+
+ private void testAvoidBadWifiConfig_controlledBySettings() {
final ContentResolver cr = mServiceContext.getContentResolver();
final String settingName = ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI;
- mPolicyTracker.mConfigRestrictsAvoidBadWifi = false;
- String[] values = new String[] {null, "0", "1"};
- for (int i = 0; i < values.length; i++) {
- Settings.Global.putInt(cr, settingName, 1);
- mPolicyTracker.reevaluate();
- waitForIdle();
- String msg = String.format("config=false, setting=%s", values[i]);
- assertTrue(mService.avoidBadWifi());
- assertFalse(msg, mPolicyTracker.shouldNotifyWifiUnvalidated());
- }
-
- mPolicyTracker.mConfigRestrictsAvoidBadWifi = true;
-
- Settings.Global.putInt(cr, settingName, 0);
+ Settings.Global.putString(cr, settingName, "0");
mPolicyTracker.reevaluate();
waitForIdle();
assertFalse(mService.avoidBadWifi());
assertFalse(mPolicyTracker.shouldNotifyWifiUnvalidated());
- Settings.Global.putInt(cr, settingName, 1);
+ Settings.Global.putString(cr, settingName, "1");
mPolicyTracker.reevaluate();
waitForIdle();
assertTrue(mService.avoidBadWifi());
@@ -4681,13 +4683,40 @@
assertTrue(mPolicyTracker.shouldNotifyWifiUnvalidated());
}
+ private void testAvoidBadWifiConfig_ignoreSettings() {
+ final ContentResolver cr = mServiceContext.getContentResolver();
+ final String settingName = ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI;
+
+ String[] values = new String[] {null, "0", "1"};
+ for (int i = 0; i < values.length; i++) {
+ Settings.Global.putString(cr, settingName, values[i]);
+ mPolicyTracker.reevaluate();
+ waitForIdle();
+ String msg = String.format("config=false, setting=%s", values[i]);
+ assertTrue(mService.avoidBadWifi());
+ assertFalse(msg, mPolicyTracker.shouldNotifyWifiUnvalidated());
+ }
+ }
+
+ @Test
+ public void testAvoidBadWifiSetting() throws Exception {
+ final ContentResolver cr = mServiceContext.getContentResolver();
+ final String settingName = ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI;
+
+ doReturn(1).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
+ testAvoidBadWifiConfig_ignoreSettings();
+
+ doReturn(0).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
+ testAvoidBadWifiConfig_controlledBySettings();
+ }
+
@Ignore("Refactoring in progress b/178071397")
@Test
public void testAvoidBadWifi() throws Exception {
final ContentResolver cr = mServiceContext.getContentResolver();
// Pretend we're on a carrier that restricts switching away from bad wifi.
- mPolicyTracker.mConfigRestrictsAvoidBadWifi = true;
+ doReturn(0).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
// File a request for cell to ensure it doesn't go down.
final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
@@ -4738,13 +4767,13 @@
// Simulate switching to a carrier that does not restrict avoiding bad wifi, and expect
// that we switch back to cell.
- mPolicyTracker.mConfigRestrictsAvoidBadWifi = false;
+ doReturn(1).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
mPolicyTracker.reevaluate();
defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
assertEquals(mCm.getActiveNetwork(), cellNetwork);
// Switch back to a restrictive carrier.
- mPolicyTracker.mConfigRestrictsAvoidBadWifi = true;
+ doReturn(0).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
mPolicyTracker.reevaluate();
defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mCm.getActiveNetwork(), wifiNetwork);
diff --git a/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
index cf2c9c7..2b5bfac 100644
--- a/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -58,11 +58,13 @@
import android.net.LinkProperties;
import android.net.Network;
import android.os.Binder;
+import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.system.Os;
import android.test.mock.MockContext;
import android.util.ArraySet;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import com.android.server.IpSecService.TunnelInterfaceRecord;
@@ -82,6 +84,7 @@
/** Unit tests for {@link IpSecService}. */
@SmallTest
@RunWith(Parameterized.class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class IpSecServiceParameterizedTest {
private static final int TEST_SPI = 0xD1201D;
diff --git a/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java b/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java
index 22a2c94..0e3b03c 100644
--- a/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java
+++ b/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java
@@ -30,9 +30,11 @@
import android.content.Context;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -52,6 +54,7 @@
/** Unit tests for {@link IpSecService.RefcountedResource}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class IpSecServiceRefcountedResourceTest {
Context mMockContext;
IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
diff --git a/tests/unit/java/com/android/server/IpSecServiceTest.java b/tests/unit/java/com/android/server/IpSecServiceTest.java
index 6232423..2dc21c0 100644
--- a/tests/unit/java/com/android/server/IpSecServiceTest.java
+++ b/tests/unit/java/com/android/server/IpSecServiceTest.java
@@ -43,6 +43,7 @@
import android.net.IpSecSpiResponse;
import android.net.IpSecUdpEncapResponse;
import android.os.Binder;
+import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.system.ErrnoException;
@@ -50,6 +51,7 @@
import android.system.StructStat;
import android.util.Range;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -71,6 +73,7 @@
/** Unit tests for {@link IpSecService}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class IpSecServiceTest {
private static final int DROID_SPI = 0xD1201D;
diff --git a/tests/unit/java/com/android/server/LegacyTypeTrackerTest.kt b/tests/unit/java/com/android/server/LegacyTypeTrackerTest.kt
index 5ec1119..750703c 100644
--- a/tests/unit/java/com/android/server/LegacyTypeTrackerTest.kt
+++ b/tests/unit/java/com/android/server/LegacyTypeTrackerTest.kt
@@ -43,7 +43,9 @@
import android.net.EthernetManager
import android.net.NetworkInfo.DetailedState.CONNECTED
import android.net.NetworkInfo.DetailedState.DISCONNECTED
+import android.os.Build
import android.telephony.TelephonyManager
+import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.server.ConnectivityService.LegacyTypeTracker
@@ -66,6 +68,7 @@
@RunWith(AndroidJUnit4::class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
class LegacyTypeTrackerTest {
private val supportedTypes = arrayOf(TYPE_WIFI, TYPE_WIFI_P2P, TYPE_ETHERNET, TYPE_MOBILE,
TYPE_MOBILE_SUPL, TYPE_MOBILE_MMS, TYPE_MOBILE_SUPL, TYPE_MOBILE_DUN, TYPE_MOBILE_HIPRI,
diff --git a/tests/unit/java/com/android/server/NetIdManagerTest.kt b/tests/unit/java/com/android/server/NetIdManagerTest.kt
index 6f5e740..5c43197 100644
--- a/tests/unit/java/com/android/server/NetIdManagerTest.kt
+++ b/tests/unit/java/com/android/server/NetIdManagerTest.kt
@@ -16,6 +16,8 @@
package com.android.server
+import android.os.Build
+import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.server.NetIdManager.MIN_NET_ID
@@ -27,6 +29,7 @@
@RunWith(AndroidJUnit4::class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
class NetIdManagerTest {
@Test
fun testReserveReleaseNetId() {
diff --git a/tests/unit/java/com/android/server/NetworkManagementServiceTest.java b/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
index 13516d7..32a8f3b 100644
--- a/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
+++ b/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
@@ -38,12 +38,14 @@
import android.net.NetworkPolicyManager;
import android.os.BatteryStats;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArrayMap;
+import androidx.test.filters.SdkSuppress;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.app.IBatteryStats;
@@ -66,6 +68,7 @@
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class NetworkManagementServiceTest {
private NetworkManagementService mNMService;
@Mock private Context mContext;
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 20be5f4..5ea0e8e 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -18,10 +18,12 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -29,17 +31,20 @@
import android.content.Context;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
+import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.NsdService.DaemonConnection;
import com.android.server.NsdService.DaemonConnectionSupplier;
import com.android.server.NsdService.NativeCallbackReceiver;
+import com.android.testutils.HandlerUtils;
import org.junit.After;
import org.junit.Before;
@@ -48,24 +53,25 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
// TODOs:
// - test client can send requests and receive replies
// - test NSD_ON ENABLE/DISABLED listening
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class NsdServiceTest {
static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
private static final long CLEANUP_DELAY_MS = 500;
-
- long mTimeoutMs = 100; // non-final so that tests can adjust the value.
+ private static final long TIMEOUT_MS = 500;
@Mock Context mContext;
@Mock ContentResolver mResolver;
@Mock NsdService.NsdSettings mSettings;
- @Mock DaemonConnection mDaemon;
NativeCallbackReceiver mDaemonCallback;
+ @Spy DaemonConnection mDaemon = new DaemonConnection(mDaemonCallback);
HandlerThread mThread;
TestHandler mHandler;
@@ -74,6 +80,7 @@
MockitoAnnotations.initMocks(this);
mThread = new HandlerThread("mock-service-handler");
mThread.start();
+ doReturn(true).when(mDaemon).execute(any());
mHandler = new TestHandler(mThread.getLooper());
when(mContext.getContentResolver()).thenReturn(mResolver);
}
@@ -95,14 +102,17 @@
// Creating an NsdManager will not cause any cmds executed, which means
// no daemon is started.
NsdManager client1 = connectClient(service);
+ waitForIdle();
verify(mDaemon, never()).execute(any());
// Creating another NsdManager will not cause any cmds executed.
NsdManager client2 = connectClient(service);
+ waitForIdle();
verify(mDaemon, never()).execute(any());
client1.disconnect();
// Still 1 client remains, daemon shouldn't be stopped.
+ waitForIdle();
verify(mDaemon, never()).maybeStop();
client2.disconnect();
@@ -116,11 +126,11 @@
@Test
public void testClientRequestsAreGCedAtDisconnection() {
when(mSettings.isEnabled()).thenReturn(true);
- when(mDaemon.execute(any())).thenReturn(true);
NsdService service = makeService();
NsdManager client = connectClient(service);
+ waitForIdle();
verify(mDaemon, never()).maybeStart();
verify(mDaemon, never()).execute(any());
@@ -130,27 +140,30 @@
// Client registration request
NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
client.registerService(request, PROTOCOL, listener1);
- verify(mDaemon, timeout(mTimeoutMs).times(1)).maybeStart();
- verifyDaemonCommand("register 2 a_name a_type 2201");
+ waitForIdle();
+ verify(mDaemon, times(1)).maybeStart();
+ verifyDaemonCommands("start-service", "register 2 a_name a_type 2201");
// Client discovery request
NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class);
client.discoverServices("a_type", PROTOCOL, listener2);
- verify(mDaemon, timeout(mTimeoutMs).times(1)).maybeStart();
+ waitForIdle();
+ verify(mDaemon, times(1)).maybeStart();
verifyDaemonCommand("discover 3 a_type");
// Client resolve request
NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
client.resolveService(request, listener3);
- verify(mDaemon, timeout(mTimeoutMs).times(1)).maybeStart();
+ waitForIdle();
+ verify(mDaemon, times(1)).maybeStart();
verifyDaemonCommand("resolve 4 a_name a_type local.");
// Client disconnects, stop the daemon after CLEANUP_DELAY_MS.
client.disconnect();
verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
-
// checks that request are cleaned
- verifyDaemonCommands("stop-register 2", "stop-discover 3", "stop-resolve 4");
+ verifyDaemonCommands("stop-register 2", "stop-discover 3",
+ "stop-resolve 4", "stop-service");
client.disconnect();
}
@@ -158,7 +171,6 @@
@Test
public void testCleanupDelayNoRequestActive() {
when(mSettings.isEnabled()).thenReturn(true);
- when(mDaemon.execute(any())).thenReturn(true);
NsdService service = makeService();
NsdManager client = connectClient(service);
@@ -167,19 +179,25 @@
request.setPort(2201);
NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
client.registerService(request, PROTOCOL, listener1);
- verify(mDaemon, timeout(mTimeoutMs).times(1)).maybeStart();
- verifyDaemonCommand("register 2 a_name a_type 2201");
+ waitForIdle();
+ verify(mDaemon, times(1)).maybeStart();
+ verifyDaemonCommands("start-service", "register 2 a_name a_type 2201");
client.unregisterService(listener1);
verifyDaemonCommand("stop-register 2");
verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
+ verifyDaemonCommand("stop-service");
reset(mDaemon);
client.disconnect();
// Client disconnects, after CLEANUP_DELAY_MS, maybeStop the daemon.
verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
}
+ private void waitForIdle() {
+ HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
+ }
+
NsdService makeService() {
DaemonConnectionSupplier supplier = (callback) -> {
mDaemonCallback = callback;
@@ -196,10 +214,11 @@
}
void verifyDelayMaybeStopDaemon(long cleanupDelayMs) {
+ waitForIdle();
// Stop daemon shouldn't be called immediately.
- verify(mDaemon, timeout(mTimeoutMs).times(0)).maybeStop();
+ verify(mDaemon, never()).maybeStop();
// Clean up the daemon after CLEANUP_DELAY_MS.
- verify(mDaemon, timeout(cleanupDelayMs + mTimeoutMs)).maybeStop();
+ verify(mDaemon, timeout(cleanupDelayMs + TIMEOUT_MS)).maybeStop();
}
void verifyDaemonCommands(String... wants) {
@@ -211,8 +230,9 @@
}
void verifyDaemonCommand(String want, int n) {
- ArgumentCaptor<Object> argumentsCaptor = ArgumentCaptor.forClass(Object.class);
- verify(mDaemon, timeout(mTimeoutMs).times(n)).execute(argumentsCaptor.capture());
+ waitForIdle();
+ final ArgumentCaptor<Object> argumentsCaptor = ArgumentCaptor.forClass(Object.class);
+ verify(mDaemon, times(n)).execute(argumentsCaptor.capture());
String got = "";
for (Object o : argumentsCaptor.getAllValues()) {
got += o + " ";
@@ -220,7 +240,7 @@
assertEquals(want, got.trim());
// rearm deamon for next command verification
reset(mDaemon);
- when(mDaemon.execute(any())).thenReturn(true);
+ doReturn(true).when(mDaemon).execute(any());
}
public static class TestHandler extends Handler {
diff --git a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
index 0ffeec9..9f0c9d6 100644
--- a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
@@ -55,10 +55,12 @@
import android.net.ResolverParamsParcel;
import android.net.RouteInfo;
import android.net.shared.PrivateDnsConfig;
+import android.os.Build;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
import android.util.SparseArray;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -85,6 +87,7 @@
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class DnsManagerTest {
static final String TEST_IFACENAME = "test_wlan0";
static final int TEST_NETID = 100;
diff --git a/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
index 45b575a..02f3da7 100644
--- a/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
@@ -19,8 +19,10 @@
import android.net.NetworkAgentConfig
import android.net.NetworkCapabilities
import android.net.NetworkScore.KEEP_CONNECTED_NONE
+import android.os.Build
import android.text.TextUtils
import android.util.ArraySet
+import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.server.connectivity.FullScore.MAX_CS_MANAGED_POLICY
@@ -40,6 +42,7 @@
@RunWith(AndroidJUnit4::class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
class FullScoreTest {
// Convenience methods
fun FullScore.withPolicies(
diff --git a/tests/unit/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/unit/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
index 70495cc..d6acea1 100644
--- a/tests/unit/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
+++ b/tests/unit/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
@@ -42,8 +42,10 @@
import android.net.metrics.RaEvent;
import android.net.metrics.ValidationProbeEvent;
import android.net.metrics.WakeupStats;
+import android.os.Build;
import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SdkSuppress;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
@@ -57,6 +59,7 @@
// TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class IpConnectivityEventBuilderTest {
@Test
diff --git a/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 8b072c4..d0038a4 100644
--- a/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -45,11 +45,13 @@
import android.net.metrics.IpReachabilityEvent;
import android.net.metrics.RaEvent;
import android.net.metrics.ValidationProbeEvent;
+import android.os.Build;
import android.os.Parcelable;
import android.system.OsConstants;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Base64;
+import androidx.test.filters.SdkSuppress;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.BitUtils;
@@ -67,6 +69,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class IpConnectivityMetricsTest {
static final IpReachabilityEvent FAKE_EV =
new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED);
diff --git a/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java
index 36e229d..3f3bfdd 100644
--- a/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -43,8 +43,10 @@
import android.net.NetworkProvider;
import android.net.NetworkScore;
import android.os.Binder;
+import android.os.Build;
import android.text.format.DateUtils;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -61,6 +63,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class LingerMonitorTest {
static final String CELLULAR = "CELLULAR";
static final String WIFI = "WIFI";
diff --git a/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java b/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
index 4c80f6a..f8ef31c 100644
--- a/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
@@ -54,6 +54,7 @@
import android.net.NetworkPolicyManager;
import android.net.NetworkTemplate;
import android.net.TelephonyNetworkSpecifier;
+import android.os.Build;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
@@ -62,6 +63,7 @@
import android.util.DataUnit;
import android.util.RecurrenceRule;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -89,6 +91,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class MultipathPolicyTrackerTest {
private static final Network TEST_NETWORK = new Network(123);
private static final int POLICY_SNOOZED = -100;
diff --git a/tests/unit/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/unit/java/com/android/server/connectivity/Nat464XlatTest.java
index 9b2a638..9204d14 100644
--- a/tests/unit/java/com/android/server/connectivity/Nat464XlatTest.java
+++ b/tests/unit/java/com/android/server/connectivity/Nat464XlatTest.java
@@ -40,9 +40,11 @@
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
+import android.os.Build;
import android.os.Handler;
import android.os.test.TestLooper;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -58,6 +60,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class Nat464XlatTest {
static final String BASE_IFACE = "test0";
diff --git a/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 50aaaee..8aad1a2 100644
--- a/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -31,10 +31,12 @@
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.os.Build;
import android.system.OsConstants;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Base64;
+import androidx.test.filters.SdkSuppress;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
@@ -54,6 +56,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class NetdEventListenerServiceTest {
private static final String EXAMPLE_IPV4 = "192.0.2.1";
private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1";
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index 3adf08c..160068f 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -39,9 +39,11 @@
import android.net.ConnectivityResources;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
+import android.os.Build;
import android.os.UserHandle;
import android.telephony.TelephonyManager;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -49,9 +51,7 @@
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -67,6 +67,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class NetworkNotificationManagerTest {
private static final String TEST_SSID = "Test SSID";
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkOfferTest.kt b/tests/unit/java/com/android/server/connectivity/NetworkOfferTest.kt
index 409f8c3..d12f1c0 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkOfferTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/NetworkOfferTest.kt
@@ -20,6 +20,8 @@
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.net.NetworkScore.KEEP_CONNECTED_NONE
+import android.os.Build
+import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import org.junit.Test
@@ -34,6 +36,7 @@
@RunWith(AndroidJUnit4::class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
class NetworkOfferTest {
val mockCallback = mock(INetworkOfferCallback::class.java)
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
index 551b94c..c35b60e 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -17,74 +17,156 @@
package com.android.server.connectivity
import android.net.NetworkCapabilities
-import android.net.NetworkRequest
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkScore.KEEP_CONNECTED_NONE
+import android.net.NetworkScore.POLICY_EXITING
+import android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY
+import android.net.NetworkScore.POLICY_YIELD_TO_BAD_WIFI
+import android.os.Build
+import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
+import com.android.server.connectivity.FullScore.POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD
+import com.android.server.connectivity.FullScore.POLICY_IS_VALIDATED
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.mock
import kotlin.test.assertEquals
-import kotlin.test.assertNull
-@RunWith(AndroidJUnit4::class)
+private fun score(vararg policies: Int) = FullScore(0,
+ policies.fold(0L) { acc, e -> acc or (1L shl e) }, KEEP_CONNECTED_NONE)
+private fun caps(transport: Int) = NetworkCapabilities.Builder().addTransportType(transport).build()
+
@SmallTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
class NetworkRankerTest {
- private val ranker = NetworkRanker()
+ private val mRanker = NetworkRanker()
- private fun makeNai(satisfy: Boolean, legacyScore: Int) =
- mock(NetworkAgentInfo::class.java).also {
- doReturn(satisfy).`when`(it).satisfies(any())
- val fs = FullScore(legacyScore, 0 /* policies */, KEEP_CONNECTED_NONE)
- doReturn(fs).`when`(it).getScore()
- val nc = NetworkCapabilities.Builder().build()
- doReturn(nc).`when`(it).getCapsNoCopy()
- }
-
- @Test
- fun testGetBestNetwork() {
- val scores = listOf(20, 50, 90, 60, 23, 68)
- val nais = scores.map { makeNai(true, it) }
- val bestNetwork = nais[2] // The one with the top score
- val someRequest = mock(NetworkRequest::class.java)
- assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais, bestNetwork))
+ private class TestScore(private val sc: FullScore, private val nc: NetworkCapabilities)
+ : NetworkRanker.Scoreable {
+ override fun getScore() = sc
+ override fun getCapsNoCopy(): NetworkCapabilities = nc
}
@Test
- fun testIgnoreNonSatisfying() {
- val nais = listOf(makeNai(true, 20), makeNai(true, 50), makeNai(false, 90),
- makeNai(false, 60), makeNai(true, 23), makeNai(false, 68))
- val bestNetwork = nais[1] // Top score that's satisfying
- val someRequest = mock(NetworkRequest::class.java)
- assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais, nais[1]))
+ fun testYieldToBadWiFiOneCell() {
+ // Only cell, it wins
+ val winner = TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
+ caps(TRANSPORT_CELLULAR))
+ val scores = listOf(winner)
+ assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
}
@Test
- fun testNoMatch() {
- val nais = listOf(makeNai(false, 20), makeNai(false, 50), makeNai(false, 90))
- val someRequest = mock(NetworkRequest::class.java)
- assertNull(ranker.getBestNetwork(someRequest, nais, null))
+ fun testYieldToBadWiFiOneCellOneBadWiFi() {
+ // Bad wifi wins against yielding validated cell
+ val winner = TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD),
+ caps(TRANSPORT_WIFI))
+ val scores = listOf(
+ winner,
+ TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
+ caps(TRANSPORT_CELLULAR))
+ )
+ assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
}
@Test
- fun testEmpty() {
- val someRequest = mock(NetworkRequest::class.java)
- assertNull(ranker.getBestNetwork(someRequest, emptyList(), null))
+ fun testYieldToBadWiFiOneCellTwoBadWiFi() {
+ // Bad wifi wins against yielding validated cell. Prefer the one that's primary.
+ val winner = TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD,
+ POLICY_TRANSPORT_PRIMARY), caps(TRANSPORT_WIFI))
+ val scores = listOf(
+ winner,
+ TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD),
+ caps(TRANSPORT_WIFI)),
+ TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
+ caps(TRANSPORT_CELLULAR))
+ )
+ assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
}
- // Make sure the ranker is "stable" (as in stable sort), that is, it always returns the FIRST
- // network satisfying the request if multiple of them have the same score.
@Test
- fun testStable() {
- val nais1 = listOf(makeNai(true, 30), makeNai(true, 30), makeNai(true, 30),
- makeNai(true, 30), makeNai(true, 30), makeNai(true, 30))
- val someRequest = mock(NetworkRequest::class.java)
- assertEquals(nais1[0], ranker.getBestNetwork(someRequest, nais1, nais1[0]))
+ fun testYieldToBadWiFiOneCellTwoBadWiFiOneNotAvoided() {
+ // Bad wifi ever validated wins against bad wifi that never was validated (or was
+ // avoided when bad).
+ val winner = TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD),
+ caps(TRANSPORT_WIFI))
+ val scores = listOf(
+ winner,
+ TestScore(score(), caps(TRANSPORT_WIFI)),
+ TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
+ caps(TRANSPORT_CELLULAR))
+ )
+ assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+ }
- val nais2 = listOf(makeNai(true, 30), makeNai(true, 50), makeNai(true, 20),
- makeNai(true, 50), makeNai(true, 50), makeNai(true, 40))
- assertEquals(nais2[1], ranker.getBestNetwork(someRequest, nais2, nais2[1]))
+ @Test
+ fun testYieldToBadWiFiOneCellOneBadWiFiOneGoodWiFi() {
+ // Good wifi wins
+ val winner = TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD,
+ POLICY_IS_VALIDATED), caps(TRANSPORT_WIFI))
+ val scores = listOf(
+ winner,
+ TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD,
+ POLICY_TRANSPORT_PRIMARY), caps(TRANSPORT_WIFI)),
+ TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
+ caps(TRANSPORT_CELLULAR))
+ )
+ assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+ }
+
+ @Test
+ fun testYieldToBadWiFiTwoCellsOneBadWiFi() {
+ // Cell that doesn't yield wins over cell that yields and bad wifi
+ val winner = TestScore(score(POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR))
+ val scores = listOf(
+ winner,
+ TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD,
+ POLICY_TRANSPORT_PRIMARY), caps(TRANSPORT_WIFI)),
+ TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
+ caps(TRANSPORT_CELLULAR))
+ )
+ assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+ }
+
+ @Test
+ fun testYieldToBadWiFiTwoCellsOneBadWiFiOneGoodWiFi() {
+ // Good wifi wins over cell that doesn't yield and cell that yields
+ val winner = TestScore(score(POLICY_IS_VALIDATED), caps(TRANSPORT_WIFI))
+ val scores = listOf(
+ winner,
+ TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD,
+ POLICY_TRANSPORT_PRIMARY), caps(TRANSPORT_WIFI)),
+ TestScore(score(POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)),
+ TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
+ caps(TRANSPORT_CELLULAR))
+ )
+ assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+ }
+
+ @Test
+ fun testYieldToBadWiFiOneExitingGoodWiFi() {
+ // Yielding cell wins over good exiting wifi
+ val winner = TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
+ caps(TRANSPORT_CELLULAR))
+ val scores = listOf(
+ winner,
+ TestScore(score(POLICY_IS_VALIDATED, POLICY_EXITING), caps(TRANSPORT_WIFI))
+ )
+ assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+ }
+
+ @Test
+ fun testYieldToBadWiFiOneExitingBadWiFi() {
+ // Yielding cell wins over bad exiting wifi
+ val winner = TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
+ caps(TRANSPORT_CELLULAR))
+ val scores = listOf(
+ winner,
+ TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD,
+ POLICY_EXITING), caps(TRANSPORT_WIFI))
+ )
+ assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
}
}
diff --git a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
index 8b45755..9c7d9c0 100644
--- a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -79,6 +79,7 @@
import android.util.SparseIntArray;
import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -100,6 +101,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class PermissionMonitorTest {
private static final UserHandle MOCK_USER1 = UserHandle.of(0);
private static final UserHandle MOCK_USER2 = UserHandle.of(1);
@@ -526,13 +528,13 @@
// MOCK_UID1: MOCK_PACKAGE1 only has network permission.
// SYSTEM_UID: SYSTEM_PACKAGE1 has system permission.
// SYSTEM_UID: SYSTEM_PACKAGE2 only has network permission.
- doReturn(SYSTEM).when(mPermissionMonitor).highestPermissionForUid(eq(SYSTEM), anyString());
doReturn(SYSTEM).when(mPermissionMonitor).highestPermissionForUid(any(),
eq(SYSTEM_PACKAGE1));
doReturn(NETWORK).when(mPermissionMonitor).highestPermissionForUid(any(),
eq(SYSTEM_PACKAGE2));
doReturn(NETWORK).when(mPermissionMonitor).highestPermissionForUid(any(),
eq(MOCK_PACKAGE1));
+ doReturn(SYSTEM).when(mPermissionMonitor).highestPermissionForUid(eq(SYSTEM), anyString());
// Add SYSTEM_PACKAGE2, expect only have network permission.
mPermissionMonitor.onUserAdded(MOCK_USER1);
@@ -547,6 +549,21 @@
netdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1, MOCK_USER2},
new int[]{SYSTEM_UID});
+ // Remove SYSTEM_PACKAGE2, expect keep system permission.
+ when(mPackageManager.getPackagesForUid(MOCK_USER1.getUid(SYSTEM_UID)))
+ .thenReturn(new String[]{SYSTEM_PACKAGE1});
+ when(mPackageManager.getPackagesForUid(MOCK_USER2.getUid(SYSTEM_UID)))
+ .thenReturn(new String[]{SYSTEM_PACKAGE1});
+ removePackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2},
+ SYSTEM_PACKAGE2, SYSTEM_UID);
+ netdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1, MOCK_USER2},
+ new int[]{SYSTEM_UID});
+
+ // Add SYSTEM_PACKAGE2, expect keep system permission.
+ addPackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, SYSTEM_PACKAGE2, SYSTEM_UID);
+ netdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1, MOCK_USER2},
+ new int[]{SYSTEM_UID});
+
addPackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1);
netdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1, MOCK_USER2},
new int[]{SYSTEM_UID});
@@ -554,6 +571,10 @@
new int[]{MOCK_UID1});
// Remove MOCK_UID1, expect no permission left for all user.
+ when(mPackageManager.getPackagesForUid(MOCK_USER1.getUid(MOCK_UID1)))
+ .thenReturn(new String[]{});
+ when(mPackageManager.getPackagesForUid(MOCK_USER2.getUid(MOCK_UID1)))
+ .thenReturn(new String[]{});
mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
removePackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1);
netdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2},
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 6ff47ae..6971b3ca 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -82,6 +82,7 @@
import android.net.VpnTransportInfo;
import android.net.ipsec.ike.IkeSessionCallback;
import android.net.ipsec.ike.exceptions.IkeProtocolException;
+import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.ConditionVariable;
@@ -96,6 +97,7 @@
import android.util.ArraySet;
import android.util.Range;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -140,6 +142,7 @@
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class VpnTest {
private static final String TAG = "VpnTest";
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsAccessTest.java b/tests/unit/java/com/android/server/net/NetworkStatsAccessTest.java
index 8b730af..84a1a8f 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsAccessTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsAccessTest.java
@@ -25,8 +25,10 @@
import android.app.admin.DevicePolicyManagerInternal;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.os.Build;
import android.telephony.TelephonyManager;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -41,6 +43,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class NetworkStatsAccessTest {
private static final String TEST_PKG = "com.example.test";
private static final int TEST_UID = 12345;
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsCollectionTest.java b/tests/unit/java/com/android/server/net/NetworkStatsCollectionTest.java
index 505ff9b..57f48f5 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsCollectionTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsCollectionTest.java
@@ -42,6 +42,7 @@
import android.net.NetworkStats;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
+import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
import android.telephony.SubscriptionPlan;
@@ -50,6 +51,7 @@
import android.util.RecurrenceRule;
import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -81,6 +83,7 @@
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class NetworkStatsCollectionTest {
private static final String TEST_FILE = "test.bin";
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
index 40d4446..4c80678 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -37,8 +37,10 @@
import android.net.NetworkStats;
import android.net.TrafficStats;
import android.net.UnderlyingNetworkInfo;
+import android.os.Build;
import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -62,6 +64,7 @@
/** Tests for {@link NetworkStatsFactory}. */
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class NetworkStatsFactoryTest extends NetworkStatsBaseTest {
private static final String CLAT_PREFIX = "v4-";
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
index 9fa1c50..7e8081b 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -39,6 +39,7 @@
import android.net.NetworkIdentity;
import android.net.NetworkStats;
import android.net.NetworkTemplate;
+import android.os.Build;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
@@ -50,6 +51,7 @@
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -70,6 +72,7 @@
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class NetworkStatsObserversTest {
private static final String TEST_IFACE = "test0";
private static final String TEST_IFACE2 = "test1";
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index ee94ae9..9b2c278 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -98,6 +98,7 @@
import android.net.TelephonyNetworkSpecifier;
import android.net.UnderlyingNetworkInfo;
import android.net.netstats.provider.INetworkStatsProviderCallback;
+import android.os.Build;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
@@ -113,6 +114,7 @@
import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -148,6 +150,7 @@
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class NetworkStatsServiceTest extends NetworkStatsBaseTest {
private static final String TAG = "NetworkStatsServiceTest";
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java b/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
index 6d2c7dc..f30a9c5 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
@@ -34,6 +34,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.net.NetworkTemplate;
+import android.os.Build;
import android.os.test.TestLooper;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhoneStateListener;
@@ -41,6 +42,8 @@
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import androidx.test.filters.SdkSuppress;
+
import com.android.internal.util.CollectionUtils;
import com.android.server.net.NetworkStatsSubscriptionsMonitor.RatTypeListener;
@@ -58,6 +61,7 @@
import java.util.concurrent.Executors;
@RunWith(JUnit4.class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public final class NetworkStatsSubscriptionsMonitorTest {
private static final int TEST_SUBID1 = 3;
private static final int TEST_SUBID2 = 5;
diff --git a/tests/unit/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java b/tests/unit/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
index ebbc0ef..406fdd8 100644
--- a/tests/unit/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
+++ b/tests/unit/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
@@ -20,7 +20,9 @@
import android.net.ipmemorystore.NetworkAttributes;
import android.net.networkstack.aidl.quirks.IPv6ProvisioningLossQuirk;
+import android.os.Build;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -35,6 +37,7 @@
/** Unit tests for {@link NetworkAttributes}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class NetworkAttributesTest {
private static final String WEIGHT_FIELD_NAME_PREFIX = "WEIGHT_";
private static final float EPSILON = 0.0001f;